Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collection Flow V2 - (WIP DO NOT MERGE) #2650

Open
wants to merge 42 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e5ca2bc
feat: initial boilerplate
chesterkmr Jul 25, 2024
28fc959
feat: implemented initial version of validator & added example
chesterkmr Jul 29, 2024
43dd5d4
fix: fixed exception
chesterkmr Jul 29, 2024
cb03f45
feat: added tests & added max length validator
chesterkmr Jul 30, 2024
030b4f9
feat: implemented pattern validator & tests
chesterkmr Jul 31, 2024
ee45bf5
feat: initial kyb v2 & added minimum validator
chesterkmr Aug 1, 2024
9d5d12d
feat: added maximum validator & fixes
chesterkmr Aug 1, 2024
877ad16
feat: added format value validator
chesterkmr Aug 1, 2024
46d37d4
chore: merge
chesterkmr Aug 3, 2024
0e708b7
feat: validation adjustments
chesterkmr Aug 3, 2024
dc153fd
feat: fixed validation for optional fields
chesterkmr Aug 3, 2024
4c47f72
feat: adedd is-step-valid rule & cleanup
chesterkmr Aug 6, 2024
de969b3
fix: fixed field definition resolving regex
chesterkmr Aug 6, 2024
dca5124
feat: implemented conditional validation logic apply
chesterkmr Aug 6, 2024
db38ff7
fix: fixed revision warnings on v2
chesterkmr Aug 7, 2024
fe1071b
feat: added version resolving
chesterkmr Aug 7, 2024
5c9992b
feat: reworked schema api
chesterkmr Aug 8, 2024
d82f22f
feat: validator fix
chesterkmr Aug 9, 2024
d2fa778
feat: refsactor
chesterkmr Aug 12, 2024
31c1571
feat: implemented on change validation
chesterkmr Aug 12, 2024
c769bc6
feat: added version to ui definition
chesterkmr Aug 13, 2024
1235de2
feat: added renderer & started initial preparation of components for v2
chesterkmr Aug 27, 2024
99377e2
feat: implemented new field connector & refactored code for v2
chesterkmr Aug 30, 2024
c66733e
feat: implemented connector for ui elements & refactor
chesterkmr Aug 30, 2024
25f4cc8
feat: added label rendering & errors rendering & input adapters & ref…
chesterkmr Aug 30, 2024
249e065
feat: implemented document validator
chesterkmr Oct 1, 2024
224e450
feat: initial implementation of document field adapter
chesterkmr Oct 3, 2024
d95037a
feat: implemented field list & dynamic indexes calculation mechanism
chesterkmr Oct 3, 2024
cf72327
feat: implemented documents validation within arrays
chesterkmr Oct 3, 2024
52c6c94
Merge branch 'dev' into illiar/feat/kyb-v2
chesterkmr Oct 5, 2024
f39882b
feat: added checkbox adapter
chesterkmr Oct 7, 2024
de50dbb
fix: autocomplete options fixes
chesterkmr Oct 7, 2024
a35ab80
feat: added nationality field
chesterkmr Oct 7, 2024
727f351
feat: added localefield
chesterkmr Oct 7, 2024
87bb8e3
feat: added country field
chesterkmr Oct 7, 2024
69d49dd
feat: added checkbox list field
chesterkmr Oct 7, 2024
e0f43bc
feat: added industries field
chesterkmr Oct 7, 2024
bb8fd16
feat: added multiselect
chesterkmr Oct 7, 2024
1cb4789
feat: added state field
chesterkmr Oct 7, 2024
7149c93
feat: added relationship field
chesterkmr Oct 7, 2024
e209f13
feat: added mcc field
chesterkmr Oct 7, 2024
b6e0c9e
feat: added tags field
chesterkmr Oct 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const DateRangePicker = ({ onChange, value, className }: TDateRangePicker
'text-muted-foreground': !value,
})}
>
<CalendarIcon className="size-4 mr-2" />
<CalendarIcon className="mr-2 size-4" />
{value?.from && value?.to && (
<>
{formatDate(value.from, 'LLL dd, y')} - {formatDate(value.to, 'LLL dd, y')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const Actions: FunctionComponent<IActionsProps> = ({
/>
<CaseOptions />
</div>
<div className={`min-h-20 flex justify-between gap-4`}>
<div className={`flex min-h-20 justify-between gap-4`}>
<div className={`flex flex-col space-y-3`}>
<h2
className={ctw(`w-full max-w-[35ch] break-all text-4xl font-semibold leading-9`, {
Expand Down
2 changes: 2 additions & 0 deletions apps/kyb-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"currency-codes": "^2.1.0",
"dayjs": "^1.11.6",
"dompurify": "^3.0.6",
"email-validator": "^2.0.4",
"emblor": "^1.4.6",
"form-data-encoder": "^3.0.0",
"i18n-iso-countries": "^7.6.0",
Expand All @@ -54,6 +55,7 @@
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.3",
"react-i18next": "^12.1.4",
"react-json-view": "^1.21.3",
"react-router-dom": "^6.11.2",
"use-debounce": "^9.0.4",
"uuid": "^9.0.0",
Expand Down
7 changes: 6 additions & 1 deletion apps/kyb-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { LoadingScreen } from '@/common/components/molecules/LoadingScreen';
import { APP_LANGUAGE_QUERY_KEY } from '@/common/consts/consts';
import { CustomerProviderFallback } from '@/components/molecules/CustomerProviderFallback';
import { AppLoadingContainer } from '@/components/organisms/AppLoadingContainer';
import { VersionResolver } from '@/components/organisms/VersionResolver';
import { CustomerProvider } from '@/components/providers/CustomerProvider';
import { useCustomerQuery } from '@/hooks/useCustomerQuery';
import { useFlowContextQuery } from '@/hooks/useFlowContextQuery';
Expand Down Expand Up @@ -29,11 +30,15 @@ export const App = () => {
loadingPlaceholder={<LoadingScreen />}
fallback={CustomerProviderFallback}
>
<RouterProvider router={router} />
<VersionResolver version={dependancyQueries[1]?.data?.version!}>
<RouterProvider router={router} />
</VersionResolver>
</CustomerProvider>
</AppLoadingContainer>
</Sentry.ErrorBoundary>
);

// return <ValidatorPOC />;
};

(window as any).toggleDevmode = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ export const FormContainer = ({ children, header }: Props) => {
<ScrollArea orientation="both" className="h-full">
<div className="text-secondary-foreground box-content flex flex-col gap-5 pl-40 pt-20">
{header ? <div>{header}</div> : null}
<div className="flex-flex-col w-full max-w-[385px]">{children}</div>
<div
className={`flex-flex-col w-full ${
localStorage.getItem('devmode') ? ' w-full' : ' max-w-[385px]'
}`}
>
{children}
</div>
</div>
</ScrollArea>
);
Expand Down
4 changes: 2 additions & 2 deletions apps/kyb-app/src/components/organisms/DynamicUI/Page/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useStateManagerContext } from '@/components/organisms/DynamicUI/StateMa
import { useDynamicUIContext } from '@/components/organisms/DynamicUI/hooks/useDynamicUIContext';
import { useRuleExecutor } from '@/components/organisms/DynamicUI/hooks/useRuleExecutor';
import { ErrorField } from '@/components/organisms/DynamicUI/rule-engines';
import { UIElement, UIPage } from '@/domains/collection-flow';
import { UIElementDefinition, UIPage } from '@/domains/collection-flow';
import { AnyChildren, AnyObject } from '@ballerine/ui';
import { useMemo } from 'react';
import { pageContext } from './page.context';
Expand All @@ -20,7 +20,7 @@ export interface PageProps {
export const Page = ({ page, children }: PageProps) => {
const { pages } = usePageResolverContext();
const definition = useMemo(() => {
const definition: UIElement<AnyObject> = {
const definition: UIElementDefinition<AnyObject> = {
type: 'page',
name: page.name,
options: {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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 { Document, UIElementDefinition, UIPage } from '@/domains/collection-flow';
import { AnyObject } from '@ballerine/ui';
import { useMemo } from 'react';

Expand All @@ -9,7 +9,7 @@ export interface PageError {
pageName: string;
stateName: string;
errors: ErrorField[];
_elements: UIElement<AnyObject>[];
_elements: UIElementDefinition<AnyObject>[];
}

export const selectDirectors = (context: AnyObject) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { DocumentsRuleEngine } from '@/components/organisms/DynamicUI/rule-engin
import { JmespathRuleEngine } from '@/components/organisms/DynamicUI/rule-engines/jmespath.rule-engine';
import { JsonLogicRuleEngine } from '@/components/organisms/DynamicUI/rule-engines/json-logic.rule-engine';
import { JsonSchemaRuleEngine } from '@/components/organisms/DynamicUI/rule-engines/json-schema.rule-engine';
import { Action, UIElement } from '@/domains/collection-flow';
import { Action, UIElementDefinition, UIPage } from '@/domains/collection-flow';
import { AnyObject } from '@ballerine/ui';

export const getDispatchableActions = (
context: AnyObject,
actions: Action[],
definition: UIElement<AnyObject>,
definition: UIElementDefinition<AnyObject>,
state: UIState,
currentPage: UIPage,
) => {
return actions.filter(action => {
const engineManager = new EngineManager([
Expand All @@ -20,6 +21,7 @@ export const getDispatchableActions = (
new JsonSchemaRuleEngine(),
new DocumentsRuleEngine(),
new JmespathRuleEngine(),
// new IsStepValidRuleEngine(),
]);

if (!action.dispatchOn.rules) return true;
Expand All @@ -33,7 +35,7 @@ export const getDispatchableActions = (
// @ts-ignore
rule?.type,
)
?.validate(context, rule, definition, state).isValid,
?.validate(context, rule, definition, state, currentPage).isValid,
)
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { usePageResolverContext } from '@/components/organisms/DynamicUI/PageResolver/hooks/usePageResolverContext';
import { useActionsHandlerContext } from '@/components/organisms/DynamicUI/StateManager/components/ActionsHandler/hooks/useActionsHandlerContext';
import { getDispatchableActions } from '@/components/organisms/DynamicUI/StateManager/components/ActionsHandler/hooks/useEventEmitterLogic/helpers/getDispatchableActions';
import { getTriggeredActions } from '@/components/organisms/DynamicUI/StateManager/components/ActionsHandler/hooks/useEventEmitterLogic/helpers/getTriggeredActions';
import { useActionDispatcher } from '@/components/organisms/DynamicUI/StateManager/components/ActionsHandler/hooks/useEventEmitterLogic/hooks/useActionDispatcher';
import { UIEventType } from '@/components/organisms/DynamicUI/StateManager/components/ActionsHandler/hooks/useEventEmitterLogic/types';
import { useStateManagerContext } from '@/components/organisms/DynamicUI/StateManager/components/StateProvider';
import { useDynamicUIContext } from '@/components/organisms/DynamicUI/hooks/useDynamicUIContext';
import { UIElement } from '@/domains/collection-flow';
import { UIElementDefinition } from '@/domains/collection-flow';
import { AnyObject } from '@ballerine/ui';
import { useCallback } from 'react';

export const useEventEmitterLogic = (elementDefinition: UIElement<AnyObject>) => {
export const useEventEmitterLogic = (elementDefinition: UIElementDefinition<AnyObject>) => {
const { actions, dispatchAction } = useActionsHandlerContext();
const { stateApi } = useStateManagerContext();
const { state } = useDynamicUIContext();
const { getDispatch } = useActionDispatcher(actions, dispatchAction);
const { currentPage } = usePageResolverContext();

const emitEvent = useCallback(
(type: UIEventType) => {
Expand All @@ -33,6 +35,7 @@ export const useEventEmitterLogic = (elementDefinition: UIElement<AnyObject>) =>
triggeredActions,
elementDefinition,
state,
currentPage!,
);

console.info(`Dispatchable actions`, {
Expand All @@ -52,7 +55,7 @@ export const useEventEmitterLogic = (elementDefinition: UIElement<AnyObject>) =>
dispatch(action);
});
},
[elementDefinition, actions, stateApi, state, getDispatch],
[elementDefinition, actions, stateApi, state, currentPage, getDispatch],
);

return emitEvent;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isErrorWithMessage } from '@ballerine/common';
import { AnyObject } from '@ballerine/ui';
import { WorkflowBrowserSDK } from '@ballerine/workflow-browser-sdk';
import { useCallback, useMemo, useState } from 'react';
import { isErrorWithMessage } from '@ballerine/common';

export interface StateMachineAPI {
invokePlugin: (pluginName: string) => Promise<void>;
Expand All @@ -22,7 +22,7 @@ export const useMachineLogic = (
try {
await machine.invokePlugin(pluginName);
} catch (error) {
console.log('Failed to invoke plugin', isErrorWithMessage(error) ? error.message : error);
console.info('Failed to invoke plugin', isErrorWithMessage(error) ? error.message : error);
} finally {
setInvokingPlugin(false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,58 @@
import { EngineManager } from '@/components/organisms/DynamicUI/StateManager/components/ActionsHandler/helpers/engine-manager';
import { usePageResolverContext } from '@/components/organisms/DynamicUI/PageResolver/hooks/usePageResolverContext';
import { UIState } from '@/components/organisms/DynamicUI/hooks/useUIStateLogic/types';
import { JsonLogicRuleEngine, RuleTestResult } from '@/components/organisms/DynamicUI/rule-engines';
import { DocumentsRuleEngine } from '@/components/organisms/DynamicUI/rule-engines/documents.rule-engine';
import { JmespathRuleEngine } from '@/components/organisms/DynamicUI/rule-engines/jmespath.rule-engine';
import { JsonSchemaRuleEngine } from '@/components/organisms/DynamicUI/rule-engines/json-schema.rule-engine';
import { Rule, UIElement } from '@/domains/collection-flow';
import { RuleTestResult } from '@/components/organisms/DynamicUI/rule-engines';
import { executeRule } from '@/components/organisms/DynamicUI/rule-engines/utils/execute-rules';
import { Rule, UIElementDefinition } from '@/domains/collection-flow';
import { AnyObject } from '@ballerine/ui';
import { useEffect, useMemo, useRef, useState } from 'react';

export const useRuleExecutor = (
context: AnyObject,
rules: Rule[],
definition: UIElement<AnyObject>,
definition: UIElementDefinition<AnyObject>,
uiState: UIState,
) => {
const uiStateRef = useRef(uiState);
const { currentPage } = usePageResolverContext();

useEffect(() => {
uiStateRef.current = uiState;
}, [uiState]);

const [executionResult, setExecutionResult] = useState<RuleTestResult[]>([]);

const rulesManager = useMemo(
() =>
new EngineManager([
new JsonLogicRuleEngine(),
// @ts-ignore
new JsonSchemaRuleEngine(),
new JmespathRuleEngine(),
new DocumentsRuleEngine(),
]),
[],
);

const executeRules = useMemo(
() =>
(context: AnyObject, rules: Rule[], definition: UIElement<AnyObject>, uiState: UIState) => {
(
context: AnyObject,
rules: Rule[],
definition: UIElementDefinition<AnyObject>,
uiState: UIState,
) => {
const executionResult =
rules?.map(rule => {
const engine = rulesManager.getEngine(rule.type);

const ctx = { ...context };

//This hack is neeeded to filter out `empty`
//TO DO: Find solution on how to define array items in schemas
// ctx.documents = ctx?.documents.filter(Boolean);

return engine?.validate(ctx, rule, definition, uiState);
return executeRule(ctx, rule, definition, uiState, currentPage!);
}) || [];

// @ts-ignore
setExecutionResult(executionResult);
},
[rulesManager],
[currentPage],
);

useEffect(() => {
executeRules(context, rules, definition, uiStateRef.current);
}, [context, rules, uiStateRef, definition, executeRules]);
}, [context, rules, uiStateRef, definition, currentPage, executeRules]);

if (import.meta.env.MODE === 'development') {
if (executionResult.length && executionResult.every(r => !r.isValid && r.errors?.length)) {
console.log('Rules execution result', executionResult);
console.info('Rules execution result', executionResult);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const useUIElementToolsLogic = (elementId: string) => {
);

const elementState = useMemo(
() => state.elements[elementId] || null,
() => state?.elements?.[elementId] || null,
[state.elements, elementId],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,35 @@ import {
ErrorField,
RuleEngine,
} from '@/components/organisms/DynamicUI/rule-engines/rule-engine.abstract';
import { Document, DocumentsValidatorRule, Rule, UIElement } from '@/domains/collection-flow';
import {
Document,
DocumentsValidatorRule,
Rule,
UIElementDefinition,
UIPage,
} from '@/domains/collection-flow';
import { AnyObject } from '@ballerine/ui';
import get from 'lodash/get';

export class DocumentsRuleEngine implements RuleEngine {
public readonly ENGINE_NAME = 'destination-engine';
private ruleManager = new EngineManager([new JmespathRuleEngine(), new JsonLogicRuleEngine()]);

validate(context: AnyObject, rule: unknown, definition: UIElement<AnyObject>, state: UIState) {
validate(
context: AnyObject,
rule: unknown,
definition: UIElementDefinition<AnyObject>,
state: UIState,
page: UIPage,
) {
if (this.isDestinationValidatorRule(rule)) {
const errors: ErrorField[] = [];

rule.value.forEach(params => {
const isRequired = this.isRule(params.required)
? this.ruleManager
?.getEngine(params.required.type)
?.validate(context, params.required, definition, state).isValid
?.validate(context, params.required, definition, state, page).isValid
: params.required;

const document = ((context.documents || []) as Document[]).find(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { UIState } from '@/components/organisms/DynamicUI/hooks/useUIStateLogic/types';
import {
RuleEngine,
RuleTestResult,
} from '@/components/organisms/DynamicUI/rule-engines/rule-engine.abstract';
import { validate } from '@/components/providers/Validator/hooks/useValidate';
import { UIElementDefinition, UIPage, ValidContextRule } from '@/domains/collection-flow';
import { transformV1UIElementsToV2UIElements } from '@/pages/CollectionFlowV2/helpers';
import { AnyObject } from '@ballerine/ui';
import jsonLogic from 'json-logic-js';

export class IsStepValidRuleEngine implements RuleEngine {
public readonly ENGINE_NAME = 'is-step-valid';

validate(
context: unknown,
_: unknown,
element: UIElementDefinition<AnyObject>,
__: UIState,
uiPage: UIPage,
): RuleTestResult {
console.info(`Executing rule engine ${this.ENGINE_NAME}`);

const uiEelemntsV2 = transformV1UIElementsToV2UIElements(uiPage?.elements || []);
const validationErrors = validate(uiEelemntsV2, context as AnyObject);

const result = { isValid: !validationErrors.length, errors: [] };

console.info(`Result of rule engine ${this.ENGINE_NAME}:`, {
isValid: !validationErrors.length,
errors: validationErrors,
});

return result;
}

test(context: unknown, rule: unknown) {
if (this.isValidContextRule(rule)) {
return jsonLogic.apply(rule.value, context as AnyObject) as boolean;
}

throw new Error(`Invalid rule provided to ${this.ENGINE_NAME}`);
}

private isValidContextRule(rule: unknown): rule is ValidContextRule {
return (
typeof rule === 'object' && rule !== null && 'type' in rule && rule.type === 'json-logic'
);
}
}
Loading
Loading