Compare commits

...

4 Commits

Author SHA1 Message Date
Ben Beckford 6659c78294 Merge branch 'main' into feat/workflow-filter-date 2026-06-12 12:10:09 -07:00
Jason Rasmussen e31d4aa909 fix: prerelease draft (#29034) 2026-06-12 12:43:19 -04:00
Daniel Dietzler 43b2d04e2c fix: version tests (#29032) 2026-06-12 15:54:42 +00:00
Ben Beckford dcd86493fc feat: date workflow filter 2026-06-11 13:48:39 -07:00
6 changed files with 113 additions and 18 deletions
+9 -1
View File
@@ -50,6 +50,7 @@ jobs:
outputs:
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
version: ${{ steps.output.outputs.version }}
rc: ${{ steps.output.outputs.rc }}
permissions: {} # No job-level permissions are needed because it uses the app-token
steps:
- id: token
@@ -81,7 +82,13 @@ jobs:
run: pnpm --silent release -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}"
- id: output
run: echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
run: |
echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
if [[ "$IMMICH_VERSION" =~ -rc\.[0-9]+$ ]]; then
echo "rc=true" >> $GITHUB_OUTPUT
else
echo "rc=false" >> $GITHUB_OUTPUT
fi
- name: Commit and tag
id: push-tag
@@ -145,6 +152,7 @@ jobs:
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
draft: true
prerelease: ${{ needs.bump_version.outputs.rc }}
tag_name: ${{ needs.bump_version.outputs.version }}
token: ${{ steps.generate-token.outputs.token }}
generate_release_notes: true
+1 -1
View File
@@ -95,7 +95,7 @@ describe('/server', () => {
major: expect.any(Number),
minor: expect.any(Number),
patch: expect.any(Number),
prerelease: null,
prerelease: expect.anything(),
});
});
});
+63
View File
@@ -183,6 +183,69 @@
},
"uiHints": ["Filter"]
},
{
"name": "assetDateFilter",
"title": "Filter by date",
"description": "Filter assets by date taken",
"types": ["AssetV1"],
"schema": {
"type": "object",
"properties": {
"startDate": {
"type": "object",
"title": "Start date",
"description": "Earliest date of assets to include",
"properties": {
"month": {
"type": "number",
"title": "Month",
"description": "Month of the year to match"
},
"day": {
"type": "number",
"title": "Day",
"description": "Day of the year to match"
},
"year": {
"type": "number",
"title": "Year",
"description": "Year to match"
}
}
},
"endDate": {
"type": "object",
"title": "End date",
"description": "Latest date of assets to include",
"properties": {
"month": {
"type": "number",
"title": "Month",
"description": "Month of the year to match"
},
"day": {
"type": "number",
"title": "Day",
"description": "Day of the year to match"
},
"year": {
"type": "number",
"title": "Year",
"description": "Year to match"
}
}
},
"recurring": {
"type": "boolean",
"default": false,
"title": "Match recurring dates",
"description": "Allow any assets with matching months/days regardless of the year"
}
},
"required": ["recurring", "startDate", "endDate"]
},
"uiHints": ["Filter"]
},
{
"name": "filterFileType",
"title": "Filter by file type",
+1
View File
@@ -14,6 +14,7 @@ declare module 'main' {
export function assetFileFilter(): I32;
export function assetMissingTimeZoneFilter(): I32;
export function assetLocationFilter(): I32;
export function assetDateFilter(): I32;
// updates
export function assetFavorite(): I32;
+30
View File
@@ -95,6 +95,36 @@ export const assetLocationFilter = () => {
});
};
export const assetDateFilter = () => {
return wrapper<
WorkflowType.AssetV1,
{
startDate: { month: number; day: number; year: number };
endDate: { month: number; day: number; year: number };
recurring: boolean;
}
>(({ config, data }) => {
const assetDate = new Date(data.asset.localDateTime);
let startDate = new Date(config.startDate.year, config.startDate.month - 1, config.startDate.day);
let endDate = new Date(config.endDate.year, config.endDate.month - 1, config.endDate.day);
if (config.recurring) {
startDate.setFullYear(assetDate.getFullYear());
endDate.setFullYear(assetDate.getFullYear());
if (endDate < startDate) {
if (assetDate > endDate) {
endDate.setFullYear(endDate.getFullYear() + 1);
} else {
startDate.setFullYear(startDate.getFullYear() - 1);
}
}
}
return { workflow: { continue: assetDate >= startDate && assetDate <= endDate } };
});
};
export const assetFavorite = () => {
return wrapper<WorkflowType.AssetV1, { inverse?: boolean }>(({ config, data }) => {
const target = config.inverse ? false : true;
+9 -16
View File
@@ -1,7 +1,6 @@
import { DateTime } from 'luxon';
import { SemVer } from 'semver';
import { defaults } from 'src/config';
import { serverVersion } from 'src/constants';
import { ReleaseChannel } from 'src/dtos/system-config.dto';
import { CronJob, JobName, JobStatus, SystemMetadataKey } from 'src/enum';
import { VersionService } from 'src/services/version.service';
@@ -23,16 +22,10 @@ describe(VersionService.name, () => {
mocks.cron.update.mockResolvedValue();
});
beforeAll(() => {
vitest.mock(import('src/constants.js'), async () => ({
...(await vitest.importActual<typeof import('src/constants.js')>('src/constants.js')),
serverVersion: new SemVer('v3.0.0'),
}));
});
afterAll(() => {
vitest.unmock(import('src/constants.js'));
});
vitest.mock(import('src/constants.js'), async (importOriginal) => ({
...(await importOriginal()),
serverVersion: new SemVer('v3.0.0'),
}));
it('should work', () => {
expect(sut).toBeDefined();
@@ -53,7 +46,7 @@ describe(VersionService.name, () => {
mocks.versionHistory.getLatest.mockResolvedValue({
id: 'version-1',
createdAt: new Date(),
version: serverVersion.toString(),
version: '3.0.0',
});
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(mocks.versionHistory.create).not.toHaveBeenCalled();
@@ -64,7 +57,7 @@ describe(VersionService.name, () => {
mocks.versionHistory.getLatest.mockResolvedValue({
id: 'version-1',
createdAt: new Date(),
version: serverVersion.toString(),
version: '3.0.0',
});
await sut.onBootstrap();
expect(mocks.cron.create).toHaveBeenCalledWith(
@@ -121,7 +114,7 @@ describe(VersionService.name, () => {
checkedAt: DateTime.utc().minus({ seconds: 60 }).toISO(),
releaseVersion: '1.0.0',
});
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse(serverVersion.toString()));
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse('v3.0.0'));
await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success);
expect(mocks.serverInfo.getLatestRelease).toHaveBeenCalled();
});
@@ -135,11 +128,11 @@ describe(VersionService.name, () => {
});
it('should not notify if the version is equal', async () => {
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse(serverVersion.toString()));
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse('v3.0.0'));
await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success);
expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.VersionCheckState, {
checkedAt: expect.any(String),
releaseVersion: serverVersion.toString(),
releaseVersion: 'v3.0.0',
});
expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled();
});