Skip to content

Commit

Permalink
fix: i##4099 move a story to another wf should not change the wf on s…
Browse files Browse the repository at this point in the history
…creen
  • Loading branch information
juanfran committed Nov 15, 2023
1 parent b8460d4 commit 75960a2
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Store } from '@ngrx/store';
import {
Observable,
combineLatest,
debounceTime,
distinctUntilChanged,
filter,
map,
Expand Down Expand Up @@ -100,6 +101,7 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {
) {
this.state.connect('storyView', this.store.select(selectStoryView));
this.state.connect('selectStory', this.store.select(selectStory));

this.state.connect(
'url',
this.routerHistory.urlChanged.pipe(
Expand Down Expand Up @@ -147,12 +149,7 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {
}

if (params.storyRef) {
return this.store.select(selectStory).pipe(
filterNil(),
map((story) => {
return story?.workflow.slug ?? 'main';
})
);
return this.storyWorkflow(Number(params.storyRef));
}

return of('main');
Expand All @@ -165,7 +162,11 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {
);

combineLatest([this.route.data, this.route.params])
.pipe(takeUntilDestroyed(this.destroyRef), distinctUntilChanged())
.pipe(
takeUntilDestroyed(this.destroyRef),
distinctUntilChanged(),
debounceTime(20)
)
.subscribe(([data, params]) => {
const url = this.state.get('url');
const project: Project = data.project as Project;
Expand Down Expand Up @@ -198,6 +199,17 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {
return route.firstChild ? this.getActiveRoute(route.firstChild) : route;
}

private storyWorkflow(storyRef: Story['ref']) {
return this.store.select(selectStory).pipe(
filterNil(),
filter((story) => story.ref === storyRef),
map((story) => {
return story?.workflow.slug ?? 'main';
}),
take(1)
);
}

private fetchStory(params: StoryParams) {
this.store.dispatch(
StoryDetailActions.initStory({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,8 @@ export const StoryDetailApiActions = createActionGroup({
'Delete attachment success': props<{
id: Attachment['id'];
}>(),
'Fetch workflow Success': props<{
workflow: Workflow;
}>(),
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
WorkflowMockFactory,
} from '@taiga/data';
import { cold, hot } from 'jest-marbles';
import { Observable } from 'rxjs';
import { EMPTY, Observable } from 'rxjs';
import { selectCurrentProject } from '~/app/modules/project/data-access/+state/selectors/project.selectors';
import { KanbanActions } from '~/app/modules/project/feature-kanban/data-access/+state/actions/kanban.actions';
import { AppService } from '~/app/services/app.service';
Expand All @@ -32,10 +32,15 @@ import {
} from '../actions/story-detail.actions';
import {
selectStory,
selectStoryView,
selectWorkflow,
} from '../selectors/story-detail.selectors';
import { StoryDetailEffects } from './story-detail.effects';
import { selectCurrentWorkflowSlug } from '~/app/modules/project/feature-kanban/data-access/+state/selectors/kanban.selectors';
import {
selectCurrentWorkflowSlug,
selectWorkflow as selectKanbanWorkflow,
} from '~/app/modules/project/feature-kanban/data-access/+state/selectors/kanban.selectors';
import { HttpErrorResponse } from '@angular/common/http';

describe('StoryDetailEffects', () => {
let actions$: Observable<Action>;
Expand Down Expand Up @@ -269,4 +274,126 @@ describe('StoryDetailEffects', () => {
expected
);
});

describe('StoryDetailEffects - loadWorkflow$', () => {
it('should fetch workflow for full-view', () => {
const projectApiService = spectator.inject(ProjectApiService);
const effects = spectator.inject(StoryDetailEffects);
const project = ProjectMockFactory();
const story = StoryDetailMockFactory();
const workflow = WorkflowMockFactory();

store.overrideSelector(selectCurrentProject, project);
store.overrideSelector(selectStoryView, 'full-view');
actions$ = hot('-a', {
a: StoryDetailApiActions.fetchStorySuccess({ story }),
});

projectApiService.getWorkflow.mockReturnValue(
cold('-b|', { b: workflow })
);

const expected = cold('--a', {
a: StoryDetailApiActions.fetchWorkflowSuccess({ workflow }),
});

expect(effects.loadWorkflow$).toSatisfyOnFlush(() => {
expect(effects.loadWorkflow$).toBeObservable(expected);
expect(projectApiService.getWorkflow).toHaveBeenCalledWith(
project.id,
story.workflow.slug
);
});
});

it('should reuse kanban workflow', () => {
const projectApiService = spectator.inject(ProjectApiService);
const effects = spectator.inject(StoryDetailEffects);
const project = ProjectMockFactory();
const story = StoryDetailMockFactory();
const kanbanWorkflow = WorkflowMockFactory();
story.workflow = kanbanWorkflow;

store.overrideSelector(selectCurrentProject, project);
store.overrideSelector(selectStoryView, 'modal-view');
store.overrideSelector(selectKanbanWorkflow, kanbanWorkflow);
actions$ = hot('-a', {
a: StoryDetailApiActions.fetchStorySuccess({ story }),
});

projectApiService.getWorkflow.mockReturnValue(
cold('-b|', { b: kanbanWorkflow })
);

const expected = cold('--a', {
a: StoryDetailApiActions.fetchWorkflowSuccess({
workflow: kanbanWorkflow,
}),
});

expect(effects.loadWorkflow$).toSatisfyOnFlush(() => {
expect(effects.loadWorkflow$).toBeObservable(expected);
expect(projectApiService.getWorkflow).not.toHaveBeenCalled();
});
});

it('should fetch workflow', () => {
const projectApiService = spectator.inject(ProjectApiService);
const effects = spectator.inject(StoryDetailEffects);
const project = ProjectMockFactory();
const story = StoryDetailMockFactory();
const workflow = WorkflowMockFactory();

store.overrideSelector(selectCurrentProject, project);
store.overrideSelector(selectStoryView, 'full-view');
store.overrideSelector(selectKanbanWorkflow, null);
actions$ = hot('-a', {
a: StoryDetailApiActions.fetchStorySuccess({ story }),
});

projectApiService.getWorkflow.mockReturnValue(
cold('-b|', { b: workflow })
);

const expected = cold('--a', {
a: StoryDetailApiActions.fetchWorkflowSuccess({ workflow }),
});

expect(effects.loadWorkflow$).toSatisfyOnFlush(() => {
expect(effects.loadWorkflow$).toBeObservable(expected);
expect(projectApiService.getWorkflow).toHaveBeenCalledWith(
project.id,
story.workflow.slug
);
});
});

it('should handle error', () => {
const projectApiService = spectator.inject(ProjectApiService);
const appService = spectator.inject(AppService);
const effects = spectator.inject(StoryDetailEffects);
const project = ProjectMockFactory();
const story = StoryDetailMockFactory();
const errorResponse = new HttpErrorResponse({ status: 404 });

store.overrideSelector(selectCurrentProject, project);
store.overrideSelector(selectStoryView, 'full-view');
actions$ = hot('-a', {
a: StoryDetailApiActions.fetchStorySuccess({ story }),
});

projectApiService.getWorkflow.mockReturnValue(
cold('-#|', {}, errorResponse)
);

const expected = cold('--a', {
a: EMPTY,
});

expect(effects.loadWorkflow$).toSatisfyOnFlush(() => {
expect(effects.loadWorkflow$).toBeObservable(expected);
expect(appService.errorManagement).toHaveBeenCalledWith(errorResponse);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@ import { fetch, pessimisticUpdate } from '@ngrx/router-store/data-persistence';
import { Store } from '@ngrx/store';
import { TuiNotification } from '@taiga-ui/core';
import { ProjectApiService } from '@taiga/api';
import { filter, map, tap } from 'rxjs';
import {
projectApiActions,
projectEventActions,
} from '~/app/modules/project/data-access/+state/actions/project.actions';
import { EMPTY, catchError, map, of, switchMap, tap } from 'rxjs';
import { projectEventActions } from '~/app/modules/project/data-access/+state/actions/project.actions';
import { selectCurrentProject } from '~/app/modules/project/data-access/+state/selectors/project.selectors';
import { KanbanActions } from '~/app/modules/project/feature-kanban/data-access/+state/actions/kanban.actions';
import {
selectWorkflow,
selectWorkflow as selectKanbanWorkflow,
selectCurrentWorkflowSlug,
} from '~/app/modules/project/feature-kanban/data-access/+state/selectors/kanban.selectors';
import { AppService } from '~/app/services/app.service';
Expand All @@ -33,7 +30,11 @@ import {
StoryDetailActions,
StoryDetailApiActions,
} from '../actions/story-detail.actions';
import { selectStory } from '../selectors/story-detail.selectors';
import {
selectStory,
selectStoryView,
selectWorkflow,
} from '../selectors/story-detail.selectors';

@Injectable()
export class StoryDetailEffects {
Expand Down Expand Up @@ -64,13 +65,49 @@ export class StoryDetailEffects {
StoryDetailApiActions.fetchStorySuccess,
StoryDetailApiActions.updateStoryWorkflowSuccess
),
concatLatestFrom(() => [this.store.select(selectWorkflow)]),
filter(([action, workflow]) => {
return action.story?.workflow?.slug !== workflow?.slug;
concatLatestFrom(() => [
this.store.select(selectCurrentProject).pipe(filterNil()),
]),
switchMap(([action, project]) => {
return this.store.select(selectStoryView).pipe(
switchMap((view) => {
const fetchProject = (workflowSlug: string) => {
return this.projectApiService
.getWorkflow(project.id, workflowSlug)
.pipe(
map((workflow) => {
return StoryDetailApiActions.fetchWorkflowSuccess({
workflow,
});
})
);
};

if (view === 'full-view') {
return fetchProject(action.story.workflow.slug);
} else {
return this.store.select(selectKanbanWorkflow).pipe(
filterNil(),
switchMap((kanbanWorkflow) => {
if (kanbanWorkflow.id === action.story.workflow.id) {
return of(
StoryDetailApiActions.fetchWorkflowSuccess({
workflow: kanbanWorkflow,
})
);
}

return fetchProject(action.story.workflow.slug);
})
);
}
})
);
}),
map(([action]) => {
const workflow = action.story?.workflow;
return projectApiActions.fetchWorkflow({ workflow: workflow?.slug });
catchError((httpResponse: HttpErrorResponse) => {
this.appService.errorManagement(httpResponse);

return EMPTY;
})
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import {
UserComment,
Workflow,
} from '@taiga/data';
import {
projectApiActions,
projectEventActions,
} from '~/app/modules/project/data-access/+state/actions/project.actions';
import { projectEventActions } from '~/app/modules/project/data-access/+state/actions/project.actions';
import {
KanbanActions,
KanbanApiActions,
Expand Down Expand Up @@ -90,7 +87,7 @@ export const reducer = createImmerReducer(
}
),
on(
projectApiActions.fetchWorkflowSuccess,
StoryDetailApiActions.fetchWorkflowSuccess,
(state, { workflow }): StoryDetailState => {
state.loadingWorkflow = false;
state.workflow = workflow;
Expand Down

0 comments on commit 75960a2

Please sign in to comment.