Skip to content

Commit

Permalink
feat: added steps order to collection flow config & fixed process tra…
Browse files Browse the repository at this point in the history
…cker in backoffice
  • Loading branch information
chesterkmr committed Oct 29, 2024
1 parent 6ad59bd commit cfad964
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ export class CollectionFlowProcessTracker implements IProcessTracker {
}

private getSteps() {
return Object.keys(this.workflow?.context?.collectionFlow?.state?.progress ?? {})?.sort(
const steps = this.workflow?.context?.collectionFlow?.config?.steps;

if (!steps?.length) return [];

// Create a map of stateName to orderNumber for efficient lookup
const stateOrderMap = new Map(steps.map(step => [step.stateName, step.orderNumber]));

// Get progress states and sort them by their corresponding orderNumber
return Object.keys(this.workflow?.context?.collectionFlow?.state?.progress ?? {}).sort(
(a, b) => {
return (
(this.workflow?.context?.collectionFlow?.state?.progress?.[a]?.isCompleted ?? 0) -
(this.workflow?.context?.collectionFlow?.state?.progress?.[b]?.isCompleted ?? 0)
);
const orderA = stateOrderMap.get(a) ?? 0;
const orderB = stateOrderMap.get(b) ?? 0;

return orderA - orderB;
},
);
}
Expand Down
1 change: 1 addition & 0 deletions apps/backoffice-v2/src/domains/workflows/fetchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export const BaseWorkflowByIdSchema = z.object({
.object({
config: z.object({
apiUrl: z.string().url(),
steps: z.array(z.object({ stateName: z.string(), orderNumber: z.number() })),
}),
state: z.object({
uiState: z.string(),
Expand Down
8 changes: 6 additions & 2 deletions apps/kyb-app/src/components/layouts/AppShell/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ import { ctw } from '@ballerine/ui';
export const Navigation = () => {
const { state } = useDynamicUIContext();
const { t } = useTranslation();
const { stateApi } = useStateManagerContext();
const { stateApi, payload } = useStateManagerContext();
const { currentPage } = usePageResolverContext();
const { customer } = useCustomer();
const { exit, isExitAvailable } = useAppExit();

const isFirstStep = currentPage?.number === 1;
const currentPageNumber = payload?.collectionFlow?.config?.steps?.find(
step => step.stateName === currentPage?.stateName,
)?.orderNumber;

const isFirstStep = currentPageNumber === 1;
const isDisabled = state.isLoading;

const onPrevious = useCallback(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ErrorField } from '@/components/organisms/DynamicUI/rule-engines';
import { findDocumentDefinitionById } from '@/components/organisms/UIRenderer/elements/JSONForm/helpers/findDefinitionByName';
import { Document, UIElement, UIPage } from '@/domains/collection-flow';
import { CollectionFlowContext } from '@/domains/collection-flow/types/flow-context.types';
import { AnyObject } from '@ballerine/ui';
import { useMemo } from 'react';

Expand All @@ -22,11 +23,15 @@ export const selectDirectorsDocuments = (context: unknown): Document[] =>
?.filter(Boolean)
?.flat() || [];

export const usePageErrors = (context: AnyObject, pages: UIPage[]): PageError[] => {
export const usePageErrors = (context: CollectionFlowContext, pages: UIPage[]): PageError[] => {
return useMemo(() => {
const pagesWithErrors: PageError[] = pages.map(page => {
const pageNumber = context?.collectionFlow?.config?.steps?.find(
step => step.stateName === page.stateName,
)?.orderNumber;

const pageErrorBase: PageError = {
page: page.number,
page: pageNumber || page.number,
pageName: page.name,
stateName: page.stateName,
errors: [],
Expand Down
2 changes: 1 addition & 1 deletion apps/kyb-app/src/pages/CollectionFlow/CollectionFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const CollectionFlow = withSessionProtected(() => {
const elements = schema?.uiSchema?.elements;
const definition = schema?.definition.definition;

const pageErrors = usePageErrors(context ?? {}, elements || []);
const pageErrors = usePageErrors(context ?? ({} as CollectionFlowContext), elements || []);
const isRevision = useMemo(
() => context?.collectionFlow?.state?.collectionFlowState === CollectionFlowStates.revision,
[context],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export const defaultContextSchema = Type.Composite([
config: Type.Optional(
Type.Object({
apiUrl: Type.String(),
steps: Type.Optional(
Type.Array(
Type.Object({
stateName: Type.String(),
orderNumber: Type.Number(),
}),
),
),
}),
),
state: Type.Optional(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {

export class CollectionFlowManager<TContext extends DefaultContextSchema> {
constructor(public context: TContext, private readonly _config?: CollectionFlowManagerConfig) {
console.log('config', _config);

if (_config && !collectionFlowConfigValidationSchema(_config)) {
throw new Error('Invalid collection flow manager config.');
}
Expand All @@ -29,6 +27,7 @@ export class CollectionFlowManager<TContext extends DefaultContextSchema> {

const config: NonNullable<DefaultContextSchema['collectionFlow']>['config'] = {
apiUrl: this._config?.apiUrl || '',
steps: this._config?.steps || [],
};

console.log('Collection Flow Context initiated with config: ', config);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DefaultContextSchema } from '@/schemas';
import { CollectionFlowManagerConfig } from '../schemas/config-schema';

export class ConfigHelper {
constructor(private context: DefaultContextSchema) {}
Expand All @@ -16,6 +17,14 @@ export class ConfigHelper {
this.context.collectionFlow.config.apiUrl = apiUrl;
}

get steps(): CollectionFlowManagerConfig['steps'] {
return this.context.collectionFlow?.config?.steps || [];
}

set steps(_) {
throw new Error('Setting steps is not allowed after initialization.');
}

override(config: NonNullable<NonNullable<DefaultContextSchema['collectionFlow']>['config']>) {
this.context.collectionFlow = {
config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const ajv = new Ajv();

const TCollectionFlowStepSchema = Type.Object({
stateName: Type.String(),
orderNumber: Type.Number(),
});

export const CollectionFlowManagerConfigSchema = Type.Object({
Expand Down
2 changes: 1 addition & 1 deletion services/workflows-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"prod": "npm run db:migrate-up && node dist/src/main",
"prod:next": "npm run db:migrate-up && npm run db:data-sync && node dist/src/main",
"start:watch": "nest start --watch",
"start:debug": "nest start --debug",
"start:debug": "nest start --debug --watch",
"build": "nest build --path=tsconfig.build.json",
"test": "jest --runInBand",
"test:unit": "cross-env SKIP_DB_SETUP_TEARDOWN=true jest --testRegex '.*\\.unit\\.test\\.ts$'",
Expand Down
2 changes: 1 addition & 1 deletion services/workflows-service/prisma/data-migrations
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
import { CollectionFlowManager } from '@ballerine/common';
import { PrismaClient } from '@prisma/client';
import { createWorkflow } from '@ballerine/workflow-core';
import { Prisma, PrismaClient, UiDefinition } from '@prisma/client';
import { env } from '../../../src/env';

export const getStepsInOrder = async (uiDefinition: UiDefinition) => {
if (!uiDefinition?.uiSchema) return [];

const { uiSchema = {}, definition } = uiDefinition as Prisma.JsonObject;
const { elements } = uiSchema as Prisma.JsonObject;

if (!elements || !definition) return [];

const stepsInOrder: string[] = [];

const stateMachine = createWorkflow({
runtimeId: '',
definition: (definition as Prisma.JsonObject).definition as any,
definitionType: 'statechart-json',
extensions: {},
workflowContext: {},
});

while (!stateMachine.getSnapshot().done) {
stepsInOrder.push(stateMachine.getSnapshot().value);
await stateMachine.sendEvent({ type: 'NEXT' });
}

return stepsInOrder.map((stepName, index) => ({ stateName: stepName, orderNumber: index + 1 }));
};

export const generateInitialCollectionFlowExample = async (
prismaClient: PrismaClient,
{
Expand Down Expand Up @@ -38,9 +65,15 @@ export const generateInitialCollectionFlowExample = async (
},
};

const uiDefinition = await prismaClient.uiDefinition.findFirst({
where: {
workflowDefinitionId,
},
});

const collectionFlowManager = new CollectionFlowManager(initialContext, {
apiUrl: env.APP_API_URL,
steps: [],
steps: await getStepsInOrder(uiDefinition as UiDefinition),
});

collectionFlowManager.start();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createWorkflow } from '@ballerine/workflow-core';
import { Prisma, UiDefinition } from '@prisma/client';

export const getStepsInOrder = async (uiDefinition: UiDefinition) => {
if (!uiDefinition?.uiSchema) return [];

const { uiSchema = {}, definition } = uiDefinition as Prisma.JsonObject;
const { elements } = uiSchema as Prisma.JsonObject;

if (!elements || !definition) return [];

const stepsInOrder: string[] = [];

const stateMachine = createWorkflow({
runtimeId: '',
definition: (definition as Prisma.JsonObject).definition as any,
definitionType: 'statechart-json',
extensions: {},
workflowContext: {},
});

while (!stateMachine.getSnapshot().done) {
stepsInOrder.push(stateMachine.getSnapshot().value);
await stateMachine.sendEvent({ type: 'NEXT' });
}

return stepsInOrder.map((stepName, index) => ({ stateName: stepName, orderNumber: index + 1 }));
};
6 changes: 3 additions & 3 deletions services/workflows-service/src/workflow/workflow.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { WorkflowTokenService } from '@/auth/workflow-token/workflow-token.servi
import { BusinessReportService } from '@/business-report/business-report.service';
import { BusinessRepository } from '@/business/business.repository';
import { BusinessService } from '@/business/business.service';
import { getStepsInOrder } from '@/collection-flow/helpers/get-steps-in-order';
import { ajv } from '@/common/ajv/ajv.validator';
import { AppLoggerService } from '@/common/app-logger/app-logger.service';
import { EntityRepository } from '@/common/entity/entity.repository';
Expand Down Expand Up @@ -89,6 +90,7 @@ import {
EndUser,
Prisma,
PrismaClient,
UiDefinition,
UiDefinitionContext,
User,
WorkflowDefinition,
Expand Down Expand Up @@ -1467,8 +1469,6 @@ export class WorkflowService {
}
}

const uiSchema = (uiDefinition as Record<string, any>)?.uiSchema;

// Initializing Collection Flow
const collectionFlowManager = new CollectionFlowManager(
{
Expand All @@ -1482,7 +1482,7 @@ export class WorkflowService {
},
{
apiUrl: env.APP_API_URL,
steps: uiSchema?.elements || [],
steps: await getStepsInOrder(uiDefinition as UiDefinition),
},
);

Expand Down

0 comments on commit cfad964

Please sign in to comment.