fix: normalize workspace status hydration
This commit is contained in:
parent
81b5a8477c
commit
b489e26665
5 changed files with 280 additions and 33 deletions
|
|
@ -90,7 +90,7 @@ function makeStory(overrides: Partial<BacklogStory> & { id: string; pbi_id: stri
|
|||
acceptance_criteria: overrides.acceptance_criteria ?? null,
|
||||
priority: overrides.priority ?? 2,
|
||||
sort_order: overrides.sort_order ?? 1,
|
||||
status: overrides.status ?? 'open',
|
||||
status: overrides.status ?? 'OPEN',
|
||||
pbi_id: overrides.pbi_id,
|
||||
sprint_id: overrides.sprint_id ?? null,
|
||||
created_at: overrides.created_at ?? new Date('2026-01-01'),
|
||||
|
|
@ -104,7 +104,7 @@ function makeTask(overrides: Partial<BacklogTask> & { id: string; story_id: stri
|
|||
description: overrides.description ?? null,
|
||||
priority: overrides.priority ?? 2,
|
||||
sort_order: overrides.sort_order ?? 1,
|
||||
status: overrides.status ?? 'todo',
|
||||
status: overrides.status ?? 'TO_DO',
|
||||
story_id: overrides.story_id,
|
||||
created_at: overrides.created_at ?? new Date('2026-01-01'),
|
||||
}
|
||||
|
|
@ -168,6 +168,27 @@ describe('hydrateSnapshot', () => {
|
|||
expect(s.loading.loadedProductId).toBe('prod-1')
|
||||
})
|
||||
|
||||
it('normaliseert API-statussen naar het interne store-contract', () => {
|
||||
useProductWorkspaceStore.getState().hydrateSnapshot(
|
||||
snapshotWith(
|
||||
[makePbi({ id: 'pbi-1', status: 'READY' as BacklogPbi['status'] })],
|
||||
{
|
||||
'pbi-1': [
|
||||
makeStory({ id: 'st-1', pbi_id: 'pbi-1', status: 'in_sprint' }),
|
||||
],
|
||||
},
|
||||
{
|
||||
'st-1': [makeTask({ id: 'tk-1', story_id: 'st-1', status: 'todo' })],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
const s = useProductWorkspaceStore.getState()
|
||||
expect(s.entities.pbisById['pbi-1'].status).toBe('ready')
|
||||
expect(s.entities.storiesById['st-1'].status).toBe('IN_SPRINT')
|
||||
expect(s.entities.tasksById['tk-1'].status).toBe('TO_DO')
|
||||
})
|
||||
|
||||
it('reset bestaande entities en relations bij her-hydratie', () => {
|
||||
useProductWorkspaceStore.getState().hydrateSnapshot(
|
||||
snapshotWith([makePbi({ id: 'old-pbi' })]),
|
||||
|
|
@ -624,6 +645,7 @@ describe('ensureTaskLoaded — zet detail-flag', () => {
|
|||
await useProductWorkspaceStore.getState().ensureTaskLoaded('t-1')
|
||||
const task = useProductWorkspaceStore.getState().entities.tasksById['t-1'] as TaskDetail
|
||||
expect(task._detail).toBe(true)
|
||||
expect(task.status).toBe('TO_DO')
|
||||
expect(task.implementation_plan).toBe('detailed plan here')
|
||||
expect(useProductWorkspaceStore.getState().loading.loadedTaskIds['t-1']).toBe(true)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ function makeStory(
|
|||
acceptance_criteria: overrides.acceptance_criteria ?? null,
|
||||
priority: overrides.priority ?? 2,
|
||||
sort_order: overrides.sort_order ?? 1,
|
||||
status: overrides.status ?? 'open',
|
||||
status: overrides.status ?? 'OPEN',
|
||||
pbi_id: overrides.pbi_id,
|
||||
sprint_id: overrides.sprint_id ?? null,
|
||||
created_at: overrides.created_at ?? new Date('2026-01-01'),
|
||||
|
|
@ -113,7 +113,7 @@ function makeTask(
|
|||
description: overrides.description ?? null,
|
||||
priority: overrides.priority ?? 2,
|
||||
sort_order: overrides.sort_order ?? 1,
|
||||
status: overrides.status ?? 'todo',
|
||||
status: overrides.status ?? 'TO_DO',
|
||||
story_id: overrides.story_id,
|
||||
sprint_id: overrides.sprint_id ?? null,
|
||||
created_at: overrides.created_at ?? new Date('2026-01-01'),
|
||||
|
|
@ -174,6 +174,20 @@ describe('hydrateSnapshot', () => {
|
|||
expect(s.context.activeProduct).toEqual({ id: 'prod-1', name: 'Product 1' })
|
||||
expect(s.loading.loadedSprintIds['sp-1']).toBe(true)
|
||||
})
|
||||
|
||||
it('normaliseert API-statussen naar het interne store-contract', () => {
|
||||
useSprintWorkspaceStore.getState().hydrateSnapshot(
|
||||
snapshotWith(
|
||||
makeSprint({ id: 'sp-1', product_id: 'prod-1' }),
|
||||
[makeStory({ id: 's-1', pbi_id: 'pbi-1', sprint_id: 'sp-1', status: 'in_sprint' })],
|
||||
{ 's-1': [makeTask({ id: 't-1', story_id: 's-1', status: 'todo' })] },
|
||||
),
|
||||
)
|
||||
|
||||
const s = useSprintWorkspaceStore.getState()
|
||||
expect(s.entities.storiesById['s-1'].status).toBe('IN_SPRINT')
|
||||
expect(s.entities.tasksById['t-1'].status).toBe('TO_DO')
|
||||
})
|
||||
})
|
||||
|
||||
describe('hydrateProductSprints', () => {
|
||||
|
|
@ -692,6 +706,7 @@ describe('ensureTaskLoaded — zet detail-flag', () => {
|
|||
't-1'
|
||||
] as SprintWorkspaceTaskDetail
|
||||
expect(task._detail).toBe(true)
|
||||
expect(task.status).toBe('TO_DO')
|
||||
expect(task.implementation_plan).toBe('detailed plan here')
|
||||
expect(useSprintWorkspaceStore.getState().loading.loadedTaskIds['t-1']).toBe(true)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@ import {
|
|||
writeStoryHint,
|
||||
writeTaskHint,
|
||||
} from './restore'
|
||||
import {
|
||||
normalizeBacklogStory,
|
||||
normalizeBacklogTask,
|
||||
normalizeProductBacklogSnapshot,
|
||||
normalizePbiStatusForStore,
|
||||
normalizeStoryStatusForStore,
|
||||
normalizeTaskStatusForStore,
|
||||
} from '@/stores/workspace-status-adapter'
|
||||
|
||||
interface ContextSlice {
|
||||
activeProduct: ActiveProduct | null
|
||||
|
|
@ -174,7 +182,8 @@ export const useProductWorkspaceStore = create<ProductWorkspaceStore>()(
|
|||
immer((set, get) => ({
|
||||
...initialState,
|
||||
|
||||
hydrateSnapshot(snapshot) {
|
||||
hydrateSnapshot(inputSnapshot) {
|
||||
const snapshot = normalizeProductBacklogSnapshot(inputSnapshot)
|
||||
set((s) => {
|
||||
if (snapshot.product) s.context.activeProduct = snapshot.product
|
||||
|
||||
|
|
@ -358,11 +367,12 @@ export const useProductWorkspaceStore = create<ProductWorkspaceStore>()(
|
|||
)
|
||||
if (requestId && get().loading.activeRequestId !== requestId) return
|
||||
if (!Array.isArray(stories)) return
|
||||
const normalizedStories = stories.map(normalizeBacklogStory)
|
||||
set((s) => {
|
||||
for (const story of stories) {
|
||||
for (const story of normalizedStories) {
|
||||
s.entities.storiesById[story.id] = story
|
||||
}
|
||||
s.relations.storyIdsByPbi[pbiId] = [...stories]
|
||||
s.relations.storyIdsByPbi[pbiId] = [...normalizedStories]
|
||||
.sort(compareStory)
|
||||
.map((st) => st.id)
|
||||
s.loading.loadedPbiIds[pbiId] = true
|
||||
|
|
@ -375,8 +385,9 @@ export const useProductWorkspaceStore = create<ProductWorkspaceStore>()(
|
|||
)
|
||||
if (requestId && get().loading.activeRequestId !== requestId) return
|
||||
if (!Array.isArray(tasks)) return
|
||||
const normalizedTasks = tasks.map(normalizeBacklogTask)
|
||||
set((s) => {
|
||||
for (const task of tasks) {
|
||||
for (const task of normalizedTasks) {
|
||||
const existing = s.entities.tasksById[task.id]
|
||||
if (existing && isDetail(existing)) {
|
||||
s.entities.tasksById[task.id] = { ...existing, ...task }
|
||||
|
|
@ -384,7 +395,7 @@ export const useProductWorkspaceStore = create<ProductWorkspaceStore>()(
|
|||
s.entities.tasksById[task.id] = task
|
||||
}
|
||||
}
|
||||
s.relations.taskIdsByStory[storyId] = [...tasks]
|
||||
s.relations.taskIdsByStory[storyId] = [...normalizedTasks]
|
||||
.sort(compareTask)
|
||||
.map((t) => t.id)
|
||||
s.loading.loadedStoryIds[storyId] = true
|
||||
|
|
@ -397,8 +408,9 @@ export const useProductWorkspaceStore = create<ProductWorkspaceStore>()(
|
|||
)
|
||||
if (requestId && get().loading.activeRequestId !== requestId) return
|
||||
if (!detail || typeof detail !== 'object') return
|
||||
const normalizedDetail = normalizeBacklogTask(detail)
|
||||
set((s) => {
|
||||
s.entities.tasksById[taskId] = { ...detail, _detail: true }
|
||||
s.entities.tasksById[taskId] = { ...normalizedDetail, _detail: true }
|
||||
s.loading.loadedTaskIds[taskId] = true
|
||||
})
|
||||
},
|
||||
|
|
@ -772,20 +784,65 @@ function sanitizePbiPayload(p: Record<string, unknown>): Partial<BacklogPbi> {
|
|||
const { entity: _e, op: _o, ...rest } = p
|
||||
void _e
|
||||
void _o
|
||||
if (typeof rest.status === 'string') {
|
||||
rest.status = normalizePbiStatusForStore(rest.status)
|
||||
}
|
||||
return rest as Partial<BacklogPbi>
|
||||
}
|
||||
|
||||
function sanitizeStoryPayload(p: Record<string, unknown>): Partial<BacklogStory> {
|
||||
const { entity: _e, op: _o, ...rest } = p
|
||||
const {
|
||||
entity: _e,
|
||||
op: _o,
|
||||
story_status,
|
||||
story_sort_order,
|
||||
story_title,
|
||||
story_code,
|
||||
...rest
|
||||
} = p
|
||||
void _e
|
||||
void _o
|
||||
if (rest.status === undefined && typeof story_status === 'string') {
|
||||
rest.status = story_status
|
||||
}
|
||||
if (rest.sort_order === undefined && typeof story_sort_order === 'number') {
|
||||
rest.sort_order = story_sort_order
|
||||
}
|
||||
if (rest.title === undefined && typeof story_title === 'string') {
|
||||
rest.title = story_title
|
||||
}
|
||||
if (rest.code === undefined && (typeof story_code === 'string' || story_code === null)) {
|
||||
rest.code = story_code
|
||||
}
|
||||
if (typeof rest.status === 'string') {
|
||||
rest.status = normalizeStoryStatusForStore(rest.status)
|
||||
}
|
||||
return rest as Partial<BacklogStory>
|
||||
}
|
||||
|
||||
function sanitizeTaskPayload(p: Record<string, unknown>): Partial<BacklogTask> {
|
||||
const { entity: _e, op: _o, ...rest } = p
|
||||
const {
|
||||
entity: _e,
|
||||
op: _o,
|
||||
task_status,
|
||||
task_sort_order,
|
||||
task_title,
|
||||
...rest
|
||||
} = p
|
||||
void _e
|
||||
void _o
|
||||
if (rest.status === undefined && typeof task_status === 'string') {
|
||||
rest.status = task_status
|
||||
}
|
||||
if (rest.sort_order === undefined && typeof task_sort_order === 'number') {
|
||||
rest.sort_order = task_sort_order
|
||||
}
|
||||
if (rest.title === undefined && typeof task_title === 'string') {
|
||||
rest.title = task_title
|
||||
}
|
||||
if (typeof rest.status === 'string') {
|
||||
rest.status = normalizeTaskStatusForStore(rest.status)
|
||||
}
|
||||
return rest as Partial<BacklogTask>
|
||||
}
|
||||
|
||||
|
|
@ -801,20 +858,24 @@ function coercePbiPayload(id: string, p: Record<string, unknown>): BacklogPbi {
|
|||
p.created_at instanceof Date
|
||||
? p.created_at
|
||||
: new Date(String(p.created_at ?? Date.now())),
|
||||
status: (p.status as BacklogPbi['status']) ?? 'ready',
|
||||
status: normalizePbiStatusForStore(String(p.status ?? 'ready')),
|
||||
}
|
||||
}
|
||||
|
||||
function coerceStoryPayload(id: string, p: Record<string, unknown>): BacklogStory {
|
||||
const status = p.status ?? p.story_status ?? 'OPEN'
|
||||
const sortOrder = p.sort_order ?? p.story_sort_order ?? 0
|
||||
const title = p.title ?? p.story_title ?? ''
|
||||
const code = p.code ?? p.story_code ?? null
|
||||
return {
|
||||
id,
|
||||
code: (p.code as string | null) ?? null,
|
||||
title: String(p.title ?? ''),
|
||||
code: (code as string | null) ?? null,
|
||||
title: String(title),
|
||||
description: (p.description as string | null | undefined) ?? null,
|
||||
acceptance_criteria: (p.acceptance_criteria as string | null | undefined) ?? null,
|
||||
priority: Number(p.priority ?? 4),
|
||||
sort_order: Number(p.sort_order ?? 0),
|
||||
status: String(p.status ?? 'open'),
|
||||
sort_order: Number(sortOrder),
|
||||
status: normalizeStoryStatusForStore(String(status)),
|
||||
pbi_id: String(p.pbi_id ?? ''),
|
||||
sprint_id: (p.sprint_id as string | null | undefined) ?? null,
|
||||
created_at:
|
||||
|
|
@ -825,13 +886,16 @@ function coerceStoryPayload(id: string, p: Record<string, unknown>): BacklogStor
|
|||
}
|
||||
|
||||
function coerceTaskPayload(id: string, p: Record<string, unknown>): BacklogTask {
|
||||
const status = p.status ?? p.task_status ?? 'TO_DO'
|
||||
const sortOrder = p.sort_order ?? p.task_sort_order ?? 0
|
||||
const title = p.title ?? p.task_title ?? ''
|
||||
return {
|
||||
id,
|
||||
title: String(p.title ?? ''),
|
||||
title: String(title),
|
||||
description: (p.description as string | null | undefined) ?? null,
|
||||
priority: Number(p.priority ?? 4),
|
||||
sort_order: Number(p.sort_order ?? 0),
|
||||
status: String(p.status ?? 'todo'),
|
||||
sort_order: Number(sortOrder),
|
||||
status: normalizeTaskStatusForStore(String(status)),
|
||||
story_id: String(p.story_id ?? ''),
|
||||
created_at:
|
||||
p.created_at instanceof Date
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ import {
|
|||
writeStoryHint,
|
||||
writeTaskHint,
|
||||
} from './restore'
|
||||
import {
|
||||
normalizeSprintTask,
|
||||
normalizeSprintWorkspaceSnapshot,
|
||||
normalizeStoryStatusForStore,
|
||||
normalizeTaskStatusForStore,
|
||||
} from '@/stores/workspace-status-adapter'
|
||||
|
||||
interface ContextSlice {
|
||||
activeProduct: ActiveProductRef | null
|
||||
|
|
@ -180,7 +186,8 @@ export const useSprintWorkspaceStore = create<SprintWorkspaceStore>()(
|
|||
immer((set, get) => ({
|
||||
...initialState,
|
||||
|
||||
hydrateSnapshot(snapshot) {
|
||||
hydrateSnapshot(inputSnapshot) {
|
||||
const snapshot = normalizeSprintWorkspaceSnapshot(inputSnapshot)
|
||||
set((s) => {
|
||||
if (snapshot.product) s.context.activeProduct = snapshot.product
|
||||
|
||||
|
|
@ -387,8 +394,9 @@ export const useSprintWorkspaceStore = create<SprintWorkspaceStore>()(
|
|||
)
|
||||
if (requestId && get().loading.activeRequestId !== requestId) return
|
||||
if (!Array.isArray(tasks)) return
|
||||
const normalizedTasks = tasks.map(normalizeSprintTask)
|
||||
set((s) => {
|
||||
for (const task of tasks) {
|
||||
for (const task of normalizedTasks) {
|
||||
const existing = s.entities.tasksById[task.id]
|
||||
if (existing && isDetail(existing)) {
|
||||
s.entities.tasksById[task.id] = { ...existing, ...task }
|
||||
|
|
@ -396,7 +404,7 @@ export const useSprintWorkspaceStore = create<SprintWorkspaceStore>()(
|
|||
s.entities.tasksById[task.id] = task
|
||||
}
|
||||
}
|
||||
s.relations.taskIdsByStory[storyId] = [...tasks]
|
||||
s.relations.taskIdsByStory[storyId] = [...normalizedTasks]
|
||||
.sort(compareTask)
|
||||
.map((t) => t.id)
|
||||
s.loading.loadedStoryIds[storyId] = true
|
||||
|
|
@ -409,8 +417,9 @@ export const useSprintWorkspaceStore = create<SprintWorkspaceStore>()(
|
|||
)
|
||||
if (requestId && get().loading.activeRequestId !== requestId) return
|
||||
if (!detail || typeof detail !== 'object') return
|
||||
const normalizedDetail = normalizeSprintTask(detail)
|
||||
set((s) => {
|
||||
s.entities.tasksById[taskId] = { ...detail, _detail: true }
|
||||
s.entities.tasksById[taskId] = { ...normalizedDetail, _detail: true }
|
||||
s.loading.loadedTaskIds[taskId] = true
|
||||
})
|
||||
},
|
||||
|
|
@ -839,16 +848,58 @@ function sanitizeSprintPayload(p: Record<string, unknown>): Partial<SprintWorksp
|
|||
}
|
||||
|
||||
function sanitizeStoryPayload(p: Record<string, unknown>): Partial<SprintWorkspaceStory> {
|
||||
const { entity: _e, op: _o, ...rest } = p
|
||||
const {
|
||||
entity: _e,
|
||||
op: _o,
|
||||
story_status,
|
||||
story_sort_order,
|
||||
story_title,
|
||||
story_code,
|
||||
...rest
|
||||
} = p
|
||||
void _e
|
||||
void _o
|
||||
if (rest.status === undefined && typeof story_status === 'string') {
|
||||
rest.status = story_status
|
||||
}
|
||||
if (rest.sort_order === undefined && typeof story_sort_order === 'number') {
|
||||
rest.sort_order = story_sort_order
|
||||
}
|
||||
if (rest.title === undefined && typeof story_title === 'string') {
|
||||
rest.title = story_title
|
||||
}
|
||||
if (rest.code === undefined && (typeof story_code === 'string' || story_code === null)) {
|
||||
rest.code = story_code
|
||||
}
|
||||
if (typeof rest.status === 'string') {
|
||||
rest.status = normalizeStoryStatusForStore(rest.status)
|
||||
}
|
||||
return rest as Partial<SprintWorkspaceStory>
|
||||
}
|
||||
|
||||
function sanitizeTaskPayload(p: Record<string, unknown>): Partial<SprintWorkspaceTask> {
|
||||
const { entity: _e, op: _o, ...rest } = p
|
||||
const {
|
||||
entity: _e,
|
||||
op: _o,
|
||||
task_status,
|
||||
task_sort_order,
|
||||
task_title,
|
||||
...rest
|
||||
} = p
|
||||
void _e
|
||||
void _o
|
||||
if (rest.status === undefined && typeof task_status === 'string') {
|
||||
rest.status = task_status
|
||||
}
|
||||
if (rest.sort_order === undefined && typeof task_sort_order === 'number') {
|
||||
rest.sort_order = task_sort_order
|
||||
}
|
||||
if (rest.title === undefined && typeof task_title === 'string') {
|
||||
rest.title = task_title
|
||||
}
|
||||
if (typeof rest.status === 'string') {
|
||||
rest.status = normalizeTaskStatusForStore(rest.status)
|
||||
}
|
||||
return rest as Partial<SprintWorkspaceTask>
|
||||
}
|
||||
|
||||
|
|
@ -881,15 +932,19 @@ function coerceStoryPayload(
|
|||
id: string,
|
||||
p: Record<string, unknown>,
|
||||
): SprintWorkspaceStory {
|
||||
const status = p.status ?? p.story_status ?? 'OPEN'
|
||||
const sortOrder = p.sort_order ?? p.story_sort_order ?? 0
|
||||
const title = p.title ?? p.story_title ?? ''
|
||||
const code = p.code ?? p.story_code ?? null
|
||||
return {
|
||||
id,
|
||||
code: (p.code as string | null) ?? null,
|
||||
title: String(p.title ?? ''),
|
||||
code: (code as string | null) ?? null,
|
||||
title: String(title),
|
||||
description: (p.description as string | null | undefined) ?? null,
|
||||
acceptance_criteria: (p.acceptance_criteria as string | null | undefined) ?? null,
|
||||
priority: Number(p.priority ?? 4),
|
||||
sort_order: Number(p.sort_order ?? 0),
|
||||
status: String(p.status ?? 'open'),
|
||||
sort_order: Number(sortOrder),
|
||||
status: normalizeStoryStatusForStore(String(status)),
|
||||
pbi_id: String(p.pbi_id ?? ''),
|
||||
sprint_id: (p.sprint_id as string | null | undefined) ?? null,
|
||||
created_at:
|
||||
|
|
@ -900,14 +955,17 @@ function coerceStoryPayload(
|
|||
}
|
||||
|
||||
function coerceTaskPayload(id: string, p: Record<string, unknown>): SprintWorkspaceTask {
|
||||
const status = p.status ?? p.task_status ?? 'TO_DO'
|
||||
const sortOrder = p.sort_order ?? p.task_sort_order ?? 0
|
||||
const title = p.title ?? p.task_title ?? ''
|
||||
return {
|
||||
id,
|
||||
code: (p.code as string | null) ?? null,
|
||||
title: String(p.title ?? ''),
|
||||
title: String(title),
|
||||
description: (p.description as string | null | undefined) ?? null,
|
||||
priority: Number(p.priority ?? 4),
|
||||
sort_order: Number(p.sort_order ?? 0),
|
||||
status: String(p.status ?? 'todo'),
|
||||
sort_order: Number(sortOrder),
|
||||
status: normalizeTaskStatusForStore(String(status)),
|
||||
story_id: String(p.story_id ?? ''),
|
||||
sprint_id: (p.sprint_id as string | null | undefined) ?? null,
|
||||
created_at:
|
||||
|
|
|
|||
88
stores/workspace-status-adapter.ts
Normal file
88
stores/workspace-status-adapter.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import {
|
||||
pbiStatusFromApi,
|
||||
pbiStatusToApi,
|
||||
storyStatusFromApi,
|
||||
taskStatusFromApi,
|
||||
} from '@/lib/task-status'
|
||||
import type {
|
||||
BacklogPbi,
|
||||
BacklogStory,
|
||||
BacklogTask,
|
||||
ProductBacklogSnapshot,
|
||||
TaskDetail,
|
||||
} from '@/stores/product-workspace/types'
|
||||
import type {
|
||||
SprintWorkspaceSnapshot,
|
||||
SprintWorkspaceStory,
|
||||
SprintWorkspaceTask,
|
||||
SprintWorkspaceTaskDetail,
|
||||
} from '@/stores/sprint-workspace/types'
|
||||
|
||||
export function normalizePbiStatusForStore(status: string): BacklogPbi['status'] {
|
||||
const dbStatus = pbiStatusFromApi(status)
|
||||
return dbStatus ? pbiStatusToApi(dbStatus) : (status as BacklogPbi['status'])
|
||||
}
|
||||
|
||||
export function normalizeStoryStatusForStore(status: string): string {
|
||||
return storyStatusFromApi(status) ?? status
|
||||
}
|
||||
|
||||
export function normalizeTaskStatusForStore(status: string): string {
|
||||
return taskStatusFromApi(status) ?? status
|
||||
}
|
||||
|
||||
export function normalizeBacklogPbi<T extends BacklogPbi>(pbi: T): T {
|
||||
const status = normalizePbiStatusForStore(pbi.status)
|
||||
return status === pbi.status ? pbi : { ...pbi, status }
|
||||
}
|
||||
|
||||
export function normalizeBacklogStory<T extends BacklogStory>(story: T): T {
|
||||
const status = normalizeStoryStatusForStore(story.status)
|
||||
return status === story.status ? story : { ...story, status }
|
||||
}
|
||||
|
||||
export function normalizeBacklogTask<T extends BacklogTask | TaskDetail>(task: T): T {
|
||||
const status = normalizeTaskStatusForStore(task.status)
|
||||
return status === task.status ? task : { ...task, status }
|
||||
}
|
||||
|
||||
export function normalizeSprintStory<T extends SprintWorkspaceStory>(story: T): T {
|
||||
const status = normalizeStoryStatusForStore(story.status)
|
||||
return status === story.status ? story : { ...story, status }
|
||||
}
|
||||
|
||||
export function normalizeSprintTask<T extends SprintWorkspaceTask | SprintWorkspaceTaskDetail>(
|
||||
task: T,
|
||||
): T {
|
||||
const status = normalizeTaskStatusForStore(task.status)
|
||||
return status === task.status ? task : { ...task, status }
|
||||
}
|
||||
|
||||
export function normalizeProductBacklogSnapshot(
|
||||
snapshot: ProductBacklogSnapshot,
|
||||
): ProductBacklogSnapshot {
|
||||
return {
|
||||
...snapshot,
|
||||
pbis: snapshot.pbis.map(normalizeBacklogPbi),
|
||||
storiesByPbi: mapRecordLists(snapshot.storiesByPbi, normalizeBacklogStory),
|
||||
tasksByStory: mapRecordLists(snapshot.tasksByStory, normalizeBacklogTask),
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeSprintWorkspaceSnapshot(
|
||||
snapshot: SprintWorkspaceSnapshot,
|
||||
): SprintWorkspaceSnapshot {
|
||||
return {
|
||||
...snapshot,
|
||||
stories: snapshot.stories.map(normalizeSprintStory),
|
||||
tasksByStory: mapRecordLists(snapshot.tasksByStory, normalizeSprintTask),
|
||||
}
|
||||
}
|
||||
|
||||
function mapRecordLists<T>(record: Record<string, T[]>, normalize: (item: T) => T): Record<string, T[]> {
|
||||
const next: Record<string, T[]> = {}
|
||||
for (const [id, list] of Object.entries(record)) {
|
||||
next[id] = list.map(normalize)
|
||||
}
|
||||
return next
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue