From 28fa274fa01b0da9223cb783f76f7cf8e94b3362 Mon Sep 17 00:00:00 2001 From: Michael Uray Date: Sun, 18 Oct 2026 06:34:54 +0000 Subject: [PATCH] fix(task): show each status's own color in the status filter without a space In All Issues (and other views without a space or selected project type) the status filter rendered each status presenter with space undefined and no project type. The presenter could therefore not find the project type's per-status configuration and fell back to the status category color, so all statuses of one category looked the same, unlike the issue list. When no space is known, the filter now resolves a project type per status via a new getStatusProjectType helper: the selected project type if any, otherwise a joined project type that configures a color or icon for the status, otherwise the first joined project type containing it. The result is passed as projectType to the presenter. Views with a space are unchanged. Fixes #5696 Signed-off-by: Michael Uray --- plugins/task-resources/package.json | 4 +- .../src/__tests__/statusProjectType.test.ts | 57 +++++++++++++++++++ .../src/components/StatusFilter.svelte | 15 +++++ .../task-resources/src/statusProjectType.ts | 41 +++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 plugins/task-resources/src/__tests__/statusProjectType.test.ts create mode 100644 plugins/task-resources/src/statusProjectType.ts diff --git a/plugins/task-resources/package.json b/plugins/task-resources/package.json index c0f894786c5..a6fe0108705 100644 --- a/plugins/task-resources/package.json +++ b/plugins/task-resources/package.json @@ -8,12 +8,14 @@ "build": "compile ui", "build:docs": "api-extractor run --local", "format": "format src", + "test": "jest --passWithNoTests --silent", "svelte-check": "do-svelte-check", "_phase:svelte-check": "do-svelte-check", "build:watch": "compile ui", "_phase:build": "compile ui", "_phase:format": "format src", - "_phase:validate": "compile validate" + "_phase:validate": "compile validate", + "_phase:test": "jest --passWithNoTests --silent" }, "devDependencies": { "svelte-loader": "^3.2.0", diff --git a/plugins/task-resources/src/__tests__/statusProjectType.test.ts b/plugins/task-resources/src/__tests__/statusProjectType.test.ts new file mode 100644 index 00000000000..bbddeeb469c --- /dev/null +++ b/plugins/task-resources/src/__tests__/statusProjectType.test.ts @@ -0,0 +1,57 @@ +// +// Copyright © 2026 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { IdMap, Ref, Status } from '@hcengineering/core' +import type { ProjectStatus, ProjectType, TaskType } from '@hcengineering/task' +import { getStatusProjectType } from '../statusProjectType' + +const status = 'status-a' as Ref +const other = 'status-b' as Ref + +function projectType (id: string, statuses: Array>): ProjectType { + return { + _id: id as Ref, + statuses: statuses.map((it) => ({ taskType: 'tt' as Ref, ...it })) + } as unknown as ProjectType +} + +function typesMap (...types: ProjectType[]): IdMap { + return new Map(types.map((it) => [it._id, it])) +} + +describe('getStatusProjectType', () => { + const plain = projectType('plain', [{ _id: status }]) + const colored = projectType('colored', [{ _id: status, color: 5 }]) + const unrelated = projectType('unrelated', [{ _id: other, color: 3 }]) + const types = typesMap(plain, colored, unrelated) + + it('uses the selected project type when present', () => { + expect(getStatusProjectType(status, unrelated._id, types, [plain._id, colored._id])).toBe(unrelated._id) + }) + + it('prefers a joined project type that configures the status color', () => { + expect(getStatusProjectType(status, undefined, types, [unrelated._id, plain._id, colored._id])).toBe(colored._id) + }) + + it('falls back to the first joined project type containing the status', () => { + expect(getStatusProjectType(status, undefined, types, [unrelated._id, plain._id])).toBe(plain._id) + }) + + it('returns undefined when no joined project type contains the status', () => { + expect(getStatusProjectType(status, undefined, types, [unrelated._id, 'missing' as Ref])).toBe( + undefined + ) + }) +}) diff --git a/plugins/task-resources/src/components/StatusFilter.svelte b/plugins/task-resources/src/components/StatusFilter.svelte index 1c1fd557918..f1c032e4c65 100644 --- a/plugins/task-resources/src/components/StatusFilter.svelte +++ b/plugins/task-resources/src/components/StatusFilter.svelte @@ -40,6 +40,7 @@ import { buildConfigLookup, getPresenter } from '@hcengineering/view-resources/src/utils' import { createEventDispatcher } from 'svelte' import { typesOfJoinedProjectsStore, selectedTaskTypeStore, selectedTypeStore, taskTypeStore, typeStore } from '..' + import { getStatusProjectType } from '../statusProjectType' export let filter: Filter export let space: Ref | undefined = undefined @@ -183,6 +184,19 @@ }, FILTER_DEBOUNCE_MS) } + // Without a space (e.g. All Issues) pick a project type per status, so its own color is shown, not the category one. + $: getPresenterProps = (status: Status): Record => + space === undefined + ? { + projectType: getStatusProjectType( + status._id, + $selectedTypeStore, + $typeStore, + $typesOfJoinedProjectsStore ?? [] + ) + } + : {} + let search: string = '' const dispatch = createEventDispatcher() @@ -237,6 +251,7 @@ {value} {...attribute.props} {space} + {...getPresenterProps(value)} disabled oneLine /> diff --git a/plugins/task-resources/src/statusProjectType.ts b/plugins/task-resources/src/statusProjectType.ts new file mode 100644 index 00000000000..23643275523 --- /dev/null +++ b/plugins/task-resources/src/statusProjectType.ts @@ -0,0 +1,41 @@ +// +// Copyright © 2026 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import type { IdMap, Ref, Status } from '@hcengineering/core' +import type { ProjectType } from '@hcengineering/task' + +/** + * Resolve the project type used to present a status when no space is known (e.g. All Issues). + * The selected project type wins; otherwise a joined project type that configures a color or icon + * for the status is preferred, falling back to the first joined project type that contains it. + * + * @public + */ +export function getStatusProjectType ( + status: Ref, + selectedProjectType: Ref | undefined, + projectTypes: IdMap, + joinedProjectTypes: Array> +): Ref | undefined { + if (selectedProjectType !== undefined) return selectedProjectType + let fallback: Ref | undefined + for (const typeId of joinedProjectTypes) { + const projectStatus = projectTypes.get(typeId)?.statuses?.find((it) => it._id === status) + if (projectStatus === undefined) continue + if (projectStatus.color !== undefined || projectStatus.icon !== undefined) return typeId + fallback = fallback ?? typeId + } + return fallback +}