@oneblink/apps-react 13.0.0-beta.10 → 13.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/OneBlinkFormBase.js +2 -1
- package/dist/OneBlinkFormBase.js.map +1 -1
- package/dist/apps/approvals-service.d.ts +1 -60
- package/dist/apps/approvals-service.js +1 -181
- package/dist/apps/approvals-service.js.map +1 -1
- package/dist/hooks/useFormIsDirty.d.ts +18 -0
- package/dist/hooks/useFormIsDirty.js +44 -1
- package/dist/hooks/useFormIsDirty.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/getFlowInstanceNodesWithMeta.d.ts +70 -0
- package/dist/services/getFlowInstanceNodesWithMeta.js +247 -0
- package/dist/services/getFlowInstanceNodesWithMeta.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFormIsDirty.js","sourceRoot":"","sources":["../../src/hooks/useFormIsDirty.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,kBAAkB,GAAiC,IAAI,GAAG,EAAE,CAAA;AAElE,MAAM,kBAAkB,GACtB,KAAK,CAAC,aAAa,CAA+B,kBAAkB,CAAC,CAAA;AAEvE,0EAA0E;AAC1E,+BAA+B;AAC/B,MAAM,0BAA0B,GAAG,KAAK,CAAC,aAAa,CAEpD,SAAS,CAAC,CAAA;AAEZ,MAAM,oBAAoB,GAAG,KAAK,CAAC,aAAa,CAAa,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AAEtE,MAAM,UAAU,gBAAgB;IAC9B,OAAO,KAAK,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAA;AAC/C,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,EAC3C,SAAS,EACT,QAAQ,GAIT;IACC,OAAO,CACL,KAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,SAAS,YAC5C,QAAQ,GACqB,CACjC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,0BAA0B,CAAC,EACzC,QAAQ,GAGT;IACC,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAC1D,GAAG,EAAE,CAAC,IAAI,GAAG,EAAmB,CACjC,CAAA;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,MAAc,EAAE,OAAgB,EAAE,EAAE;QACtE,kBAAkB,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,OAAO,EAAE,CAAC;gBACpC,OAAO,OAAO,CAAA;YAChB,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAA;YAC7B,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YACzB,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE;YACV,kBAAkB,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACzB,OAAO,OAAO,CAAA;gBAChB,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAA;gBAC7B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBACnB,OAAO,IAAI,CAAA;YACb,CAAC,CAAC,CAAA;QACJ,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,CACL,KAAC,0BAA0B,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YAClD,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,eAAe,YAChD,QAAQ,GACmB,GACM,CACvC,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;;IAC3C,OAAO,MAAA,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,mCAAI,KAAK,CAAA;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc,EAAE,OAAgB;IACrE,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAA;IAE7D,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE;QACzB,OAAO,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AACjC,CAAC","sourcesContent":["import * as React from 'react'\n\nconst emptyDirtyByFormId: ReadonlyMap<number, boolean> = new Map()\n\nconst FormIsDirtyContext =\n React.createContext<ReadonlyMap<number, boolean>>(emptyDirtyByFormId)\n\n// Kept separate from the value so registering does not re-render the form\n// every time the flag changes.\nconst RegisterFormIsDirtyContext = React.createContext<\n ((formId: number, isDirty: boolean) => () => void) | undefined\n>(undefined)\n\nconst FormMarkDirtyContext = React.createContext<() => void>(() => {})\n\nexport function useMarkFormDirty(): () => void {\n return React.useContext(FormMarkDirtyContext)\n}\n\nexport function FormMarkDirtyContextProvider({\n markDirty,\n children,\n}: {\n markDirty: () => void\n children: React.ReactNode\n}) {\n return (\n <FormMarkDirtyContext.Provider value={markDirty}>\n {children}\n </FormMarkDirtyContext.Provider>\n )\n}\n\n/**\n * Optional context that lets a host (for example Approvals) read whether a\n * rendered form has unsaved user edits. Derived writes (`isDerivedChange`) do\n * not set this.\n *\n * Wrap both the form and the host action so they share one registry. Each form\n * registers under its `formId`, so multiple forms under the same provider (for\n * example an approval form in a dialog) keep independent flags. If nothing is\n * registered for a form, {@link useFormIsDirty} is `false`.\n *\n * @param props\n * @returns\n * @group Components\n */\nexport function FormIsDirtyContextProvider({\n children,\n}: {\n children: React.ReactNode\n}) {\n // Must be state, not a ref. Hosts reading this are siblings of the form, so\n // they only re-render if the context value itself changes.\n const [isDirtyByFormId, setIsDirtyByFormId] = React.useState(\n () => new Map<number, boolean>(),\n )\n\n const register = React.useCallback((formId: number, isDirty: boolean) => {\n setIsDirtyByFormId((current) => {\n if (current.get(formId) === isDirty) {\n return current\n }\n const next = new Map(current)\n next.set(formId, isDirty)\n return next\n })\n return () => {\n setIsDirtyByFormId((current) => {\n if (!current.has(formId)) {\n return current\n }\n const next = new Map(current)\n next.delete(formId)\n return next\n })\n }\n }, [])\n\n return (\n <RegisterFormIsDirtyContext.Provider value={register}>\n <FormIsDirtyContext.Provider value={isDirtyByFormId}>\n
|
|
1
|
+
{"version":3,"file":"useFormIsDirty.js","sourceRoot":"","sources":["../../src/hooks/useFormIsDirty.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,MAAM,kBAAkB,GAAiC,IAAI,GAAG,EAAE,CAAA;AAElE,MAAM,kBAAkB,GACtB,KAAK,CAAC,aAAa,CAA+B,kBAAkB,CAAC,CAAA;AAEvE,0EAA0E;AAC1E,+BAA+B;AAC/B,MAAM,0BAA0B,GAAG,KAAK,CAAC,aAAa,CAEpD,SAAS,CAAC,CAAA;AAEZ,MAAM,oBAAoB,GAAG,KAAK,CAAC,aAAa,CAAa,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AAEtE,MAAM,kCAAkC,GAAG,KAAK,CAAC,aAAa,CAE5D,SAAS,CAAC,CAAA;AAEZ,MAAM,0BAA0B,GAAG,KAAK,CAAC,aAAa,CAEpD,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;AAEX,MAAM,UAAU,gBAAgB;IAC9B,OAAO,KAAK,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAA;AAC/C,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,EAC3C,SAAS,EACT,QAAQ,GAIT;IACC,OAAO,CACL,KAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,SAAS,YAC5C,QAAQ,GACqB,CACjC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,0BAA0B,CAAC,EACzC,QAAQ,GAGT;IACC,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAC1D,GAAG,EAAE,CAAC,IAAI,GAAG,EAAmB,CACjC,CAAA;IAED,2EAA2E;IAC3E,2BAA2B;IAC3B,MAAM,uBAAuB,GAAG,KAAK,CAAC,MAAM,CAC1C,IAAI,GAAG,EAAsB,CAC9B,CAAC,OAAO,CAAA;IAET,MAAM,uBAAuB,GAAG,KAAK,CAAC,WAAW,CAC/C,CAAC,MAAc,EAAE,eAA2B,EAAE,EAAE;QAC9C,uBAAuB,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;QACpD,OAAO,GAAG,EAAE;YACV,IAAI,uBAAuB,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,eAAe,EAAE,CAAC;gBAC5D,uBAAuB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACxC,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EACD,CAAC,uBAAuB,CAAC,CAC1B,CAAA;IAED,MAAM,eAAe,GAAG,KAAK,CAAC,WAAW,CACvC,CAAC,MAAc,EAAE,EAAE;;QACjB,MAAA,uBAAuB,CAAC,GAAG,CAAC,MAAM,CAAC,2CAAI,CAAA;IACzC,CAAC,EACD,CAAC,uBAAuB,CAAC,CAC1B,CAAA;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,MAAc,EAAE,OAAgB,EAAE,EAAE;QACtE,kBAAkB,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,OAAO,EAAE,CAAC;gBACpC,OAAO,OAAO,CAAA;YAChB,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAA;YAC7B,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YACzB,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;QACF,OAAO,GAAG,EAAE;YACV,kBAAkB,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBACzB,OAAO,OAAO,CAAA;gBAChB,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAA;gBAC7B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;gBACnB,OAAO,IAAI,CAAA;YACb,CAAC,CAAC,CAAA;QACJ,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,CACL,KAAC,0BAA0B,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YAClD,KAAC,kCAAkC,CAAC,QAAQ,IAC1C,KAAK,EAAE,uBAAuB,YAE9B,KAAC,0BAA0B,CAAC,QAAQ,IAAC,KAAK,EAAE,eAAe,YACzD,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,eAAe,YAChD,QAAQ,GACmB,GACM,GACM,GACV,CACvC,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;;IAC3C,OAAO,MAAA,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,mCAAI,KAAK,CAAA;AAClE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,MAAM,eAAe,GAAG,KAAK,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAA;IAEpE,OAAO,KAAK,CAAC,WAAW,CACtB,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,EAC7B,CAAC,eAAe,EAAE,MAAM,CAAC,CAC1B,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc,EAAE,OAAgB;IACrE,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAA;IAE7D,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE;QACzB,OAAO,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAC5C,MAAc,EACd,eAA2B;IAE3B,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAA;IAErE,KAAK,CAAC,eAAe,CAAC,GAAG,EAAE;QACzB,OAAO,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,MAAM,EAAE,eAAe,CAAC,CAAA;IAC5C,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAA;AACzC,CAAC","sourcesContent":["import * as React from 'react'\n\nconst emptyDirtyByFormId: ReadonlyMap<number, boolean> = new Map()\n\nconst FormIsDirtyContext =\n React.createContext<ReadonlyMap<number, boolean>>(emptyDirtyByFormId)\n\n// Kept separate from the value so registering does not re-render the form\n// every time the flag changes.\nconst RegisterFormIsDirtyContext = React.createContext<\n ((formId: number, isDirty: boolean) => () => void) | undefined\n>(undefined)\n\nconst FormMarkDirtyContext = React.createContext<() => void>(() => {})\n\nconst RegisterAllowFormNavigationContext = React.createContext<\n ((formId: number, allowNavigation: () => void) => () => void) | undefined\n>(undefined)\n\nconst AllowFormNavigationContext = React.createContext<\n (formId: number) => void\n>(() => {})\n\nexport function useMarkFormDirty(): () => void {\n return React.useContext(FormMarkDirtyContext)\n}\n\nexport function FormMarkDirtyContextProvider({\n markDirty,\n children,\n}: {\n markDirty: () => void\n children: React.ReactNode\n}) {\n return (\n <FormMarkDirtyContext.Provider value={markDirty}>\n {children}\n </FormMarkDirtyContext.Provider>\n )\n}\n\n/**\n * Optional context that lets a host (for example Approvals) read whether a\n * rendered form has unsaved user edits. Derived writes (`isDerivedChange`) do\n * not set this.\n *\n * Wrap both the form and the host action so they share one registry. Each form\n * registers under its `formId`, so multiple forms under the same provider (for\n * example an approval form in a dialog) keep independent flags. If nothing is\n * registered for a form, {@link useFormIsDirty} is `false`.\n *\n * @param props\n * @returns\n * @group Components\n */\nexport function FormIsDirtyContextProvider({\n children,\n}: {\n children: React.ReactNode\n}) {\n // Must be state, not a ref. Hosts reading this are siblings of the form, so\n // they only re-render if the context value itself changes.\n const [isDirtyByFormId, setIsDirtyByFormId] = React.useState(\n () => new Map<number, boolean>(),\n )\n\n // Kept in a ref, not state, so registering a form does not re-render hosts\n // reading the dirty flags.\n const allowNavigationByFormId = React.useRef(\n new Map<number, () => void>(),\n ).current\n\n const registerAllowNavigation = React.useCallback(\n (formId: number, allowNavigation: () => void) => {\n allowNavigationByFormId.set(formId, allowNavigation)\n return () => {\n if (allowNavigationByFormId.get(formId) === allowNavigation) {\n allowNavigationByFormId.delete(formId)\n }\n }\n },\n [allowNavigationByFormId],\n )\n\n const allowNavigation = React.useCallback(\n (formId: number) => {\n allowNavigationByFormId.get(formId)?.()\n },\n [allowNavigationByFormId],\n )\n\n const register = React.useCallback((formId: number, isDirty: boolean) => {\n setIsDirtyByFormId((current) => {\n if (current.get(formId) === isDirty) {\n return current\n }\n const next = new Map(current)\n next.set(formId, isDirty)\n return next\n })\n return () => {\n setIsDirtyByFormId((current) => {\n if (!current.has(formId)) {\n return current\n }\n const next = new Map(current)\n next.delete(formId)\n return next\n })\n }\n }, [])\n\n return (\n <RegisterFormIsDirtyContext.Provider value={register}>\n <RegisterAllowFormNavigationContext.Provider\n value={registerAllowNavigation}\n >\n <AllowFormNavigationContext.Provider value={allowNavigation}>\n <FormIsDirtyContext.Provider value={isDirtyByFormId}>\n {children}\n </FormIsDirtyContext.Provider>\n </AllowFormNavigationContext.Provider>\n </RegisterAllowFormNavigationContext.Provider>\n </RegisterFormIsDirtyContext.Provider>\n )\n}\n\n/**\n * Whether the form with `formId` has unsaved user edits. Safe to call without a\n * provider: returns `false`.\n *\n * @param formId\n * The id of the form whose dirty flag to read.\n * @returns\n * @group Hooks\n */\nexport function useFormIsDirty(formId: number): boolean {\n return React.useContext(FormIsDirtyContext).get(formId) ?? false\n}\n\n/**\n * Returns a callback that stops the form with `formId` prompting about unsaved\n * changes when the host navigates away. Call it after the host action that\n * discarded or persisted those edits has completed, immediately before\n * navigating. Safe to call without a provider or for a form that is not\n * rendered: nothing happens.\n *\n * @param formId\n * The id of the form whose unsaved changes prompt to stop.\n * @returns\n * @group Hooks\n */\nexport function useAllowFormNavigation(formId: number): () => void {\n const allowNavigation = React.useContext(AllowFormNavigationContext)\n\n return React.useCallback(\n () => allowNavigation(formId),\n [allowNavigation, formId],\n )\n}\n\n/**\n * Registers this form’s unsaved-changes flag with the nearest\n * {@link FormIsDirtyContextProvider}. Used internally by `OneBlinkFormBase`.\n */\nexport function useRegisterFormIsDirty(formId: number, isDirty: boolean): void {\n const register = React.useContext(RegisterFormIsDirtyContext)\n\n React.useLayoutEffect(() => {\n return register?.(formId, isDirty)\n }, [register, formId, isDirty])\n}\n\n/**\n * Registers the callback {@link useAllowFormNavigation} calls for this form.\n * Used internally by `OneBlinkFormBase`.\n */\nexport function useRegisterAllowFormNavigation(\n formId: number,\n allowNavigation: () => void,\n): void {\n const register = React.useContext(RegisterAllowFormNavigationContext)\n\n React.useLayoutEffect(() => {\n return register?.(formId, allowNavigation)\n }, [register, formId, allowNavigation])\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export { default as useFormSubmissionAutoSaveState } from './hooks/useFormSubmis
|
|
|
26
26
|
export { default as useGoogleJsApiLoader } from './hooks/useGoogleJsApiLoader';
|
|
27
27
|
export { default as useFormSubmissionDuration } from './hooks/useFormSubmissionDuration';
|
|
28
28
|
export { FormSubmissionAttemptContextProvider, useFormSubmissionAttempt, FormSubmissionAttempt, FormSubmissionAttemptAllowNavigation, } from './hooks/useFormSubmissionAttempt';
|
|
29
|
-
export { FormIsDirtyContextProvider, useFormIsDirty, } from './hooks/useFormIsDirty';
|
|
29
|
+
export { FormIsDirtyContextProvider, useFormIsDirty, useAllowFormNavigation, } from './hooks/useFormIsDirty';
|
|
30
30
|
export { default as ProgressBar } from './components/renderer/ProgressBar';
|
|
31
31
|
export { default as LoadingWithMessage } from './components/LoadingWithMessage';
|
|
32
32
|
export { default as PaymentReceipt } from './PaymentReceipt';
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ export { default as useFormSubmissionAutoSaveState } from './hooks/useFormSubmis
|
|
|
26
26
|
export { default as useGoogleJsApiLoader } from './hooks/useGoogleJsApiLoader';
|
|
27
27
|
export { default as useFormSubmissionDuration } from './hooks/useFormSubmissionDuration';
|
|
28
28
|
export { FormSubmissionAttemptContextProvider, useFormSubmissionAttempt, } from './hooks/useFormSubmissionAttempt';
|
|
29
|
-
export { FormIsDirtyContextProvider, useFormIsDirty, } from './hooks/useFormIsDirty';
|
|
29
|
+
export { FormIsDirtyContextProvider, useFormIsDirty, useAllowFormNavigation, } from './hooks/useFormIsDirty';
|
|
30
30
|
export { default as ProgressBar } from './components/renderer/ProgressBar';
|
|
31
31
|
export { default as LoadingWithMessage } from './components/LoadingWithMessage';
|
|
32
32
|
export { default as PaymentReceipt } from './PaymentReceipt';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,wBAAwB,IAAI,YAAY,EACxC,sBAAsB,GACvB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,kDAAkD,CAAA;AAC5F,OAAO,EAAE,OAAO,IAAI,mCAAmC,EAAE,MAAM,4DAA4D,CAAA;AAC3H,OAAO,EAAE,OAAO,IAAI,8BAA8B,EAAE,MAAM,uDAAuD,CAAA;AACjH,OAAO,EAAE,OAAO,IAAI,+BAA+B,EAAE,MAAM,wDAAwD,CAAA;AACnH,OAAO,EAAE,OAAO,IAAI,8BAA8B,EAAE,MAAM,uDAAuD,CAAA;AACjH,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,+CAA+C,CAAA;AAEjG,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAC9D,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACpE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AAClF,OAAO,EACL,OAAO,IAAI,YAAY,EACvB,wBAAwB,GACzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAiB,MAAM,kBAAkB,CAAA;AACrE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EACL,OAAO,IAAI,OAAO,EAClB,mBAAmB,GAEpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,OAAO,IAAI,qBAAqB,EAChC,iCAAiC,GAElC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,OAAO,IAAI,SAAS,EACpB,qBAAqB,GAEtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,OAAO,IAAI,gBAAgB,GAE5B,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAChF,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AAClF,OAAO,EAAE,OAAO,IAAI,8BAA8B,EAAE,MAAM,wCAAwC,CAAA;AAClG,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AAC9E,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AACxF,OAAO,EACL,oCAAoC,EACpC,wBAAwB,GAGzB,MAAM,kCAAkC,CAAA;AACzC,OAAO,EACL,0BAA0B,EAC1B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export {\n OneBlinkFormBaseProps,\n OneBlinkFormControlledProps,\n OneBlinkFormProps,\n OneBlinkFormUncontrolled as OneBlinkForm,\n OneBlinkFormControlled,\n} from './OneBlinkForm'\nexport { default as OneBlinkAutoSaveForm } from './OneBlinkAutoSaveForm'\nexport { default as OneBlinkReadOnlyForm } from './OneBlinkReadOnlyForm'\nexport { OneBlinkFormStoreProvider } from './components/formStore/OneBlinkFormStoreProvider'\nexport { default as OneBlinkFormStoreClearFiltersButton } from './components/formStore/OneBlinkFormStoreClearFiltersButton'\nexport { default as OneBlinkFormStoreColumnsButton } from './components/formStore/OneBlinkFormStoreColumnsButton'\nexport { default as OneBlinkFormStoreDownloadButton } from './components/formStore/OneBlinkFormStoreDownloadButton'\nexport { default as OneBlinkFormStoreRefreshButton } from './components/formStore/OneBlinkFormStoreRefreshButton'\nexport { default as OneBlinkFormStoreTable } from './components/formStore/OneBlinkFormStoreTable'\n\nexport { default as useIsMounted } from './hooks/useIsMounted'\nexport { default as useResendCoolDown } from './hooks/useResendCoolDown'\nexport { default as useBooleanState } from './hooks/useBooleanState'\nexport { default as useNullableState } from './hooks/useNullableState'\nexport { default as useClickOutsideElement } from './hooks/useClickOutsideElement'\nexport {\n default as useIsOffline,\n IsOfflineContextProvider,\n} from './hooks/useIsOffline'\nexport { default as useLogin, UseLoginValue } from './hooks/useLogin'\nexport { default as useSignUp } from './hooks/useSignUp'\nexport {\n default as useAuth,\n AuthContextProvider,\n AuthContextValue,\n} from './hooks/useAuth'\nexport {\n default as usePendingSubmissions,\n PendingSubmissionsContextProvider,\n PendingSubmissionsContextValue,\n} from './hooks/usePendingSubmissions'\nexport {\n default as useDrafts,\n DraftsContextProvider,\n DraftsContextValue,\n} from './hooks/useDrafts'\nexport {\n default as useLoadDataState,\n LoadDataState,\n} from './hooks/useLoadDataState'\nexport { default as useLoadResourcesState } from './hooks/useLoadResourcesState'\nexport { default as useLoadDataEffect } from './hooks/useLoadDataEffect'\nexport { default as useFormSubmissionState } from './hooks/useFormSubmissionState'\nexport { default as useFormSubmissionAutoSaveState } from './hooks/useFormSubmissionAutoSaveState'\nexport { default as useGoogleJsApiLoader } from './hooks/useGoogleJsApiLoader'\nexport { default as useFormSubmissionDuration } from './hooks/useFormSubmissionDuration'\nexport {\n FormSubmissionAttemptContextProvider,\n useFormSubmissionAttempt,\n FormSubmissionAttempt,\n FormSubmissionAttemptAllowNavigation,\n} from './hooks/useFormSubmissionAttempt'\nexport {\n FormIsDirtyContextProvider,\n useFormIsDirty,\n useAllowFormNavigation,\n} from './hooks/useFormIsDirty'\n\nexport { default as ProgressBar } from './components/renderer/ProgressBar'\nexport { default as LoadingWithMessage } from './components/LoadingWithMessage'\nexport { default as PaymentReceipt } from './PaymentReceipt'\nexport { default as PaymentForm } from './components/payments/PaymentForm'\n\nexport { default as CalendarBookingsForm } from './components/calendar-bookings/CalendarBookingsForm'\nexport { default as CalendarBookingsRescheduleForm } from './components/calendar-bookings/CalendarBookingsReschedulingForm'\nexport { default as CalendarBookingsCancelForm } from './components/calendar-bookings/CalendarBookingsCancelForm'\n\nexport { default as MultiFactorAuthentication } from './components/mfa/MultiFactorAuthentication'\nexport { default as MfaAuthenticatorAppDialog } from './components/mfa/MfaAuthenticatorAppDialog'\nexport { default as MfaDisableDialog } from './components/mfa/MfaDisableDialog'\nexport { default as MfaRemovePhoneNumberDialog } from './components/mfa/MfaRemovePhoneNumberDialog'\nexport { default as MfaPhoneNumberDialog } from './components/mfa/MfaPhoneNumberDialog'\nexport { default as MfaSuccessSnackbar } from './components/mfa/MfaSuccessSnackbar'\nexport { default as MfaErrorSnackbar } from './components/mfa/MfaErrorSnackbar'\nexport { default as MfaStatusChip } from './components/mfa/MfaStatusChip'\nexport {\n default as useMfa,\n MfaProvider,\n useUserMeetsMfaRequirement,\n} from './hooks/useMfa'\n\nexport { default as DownloadableFiles } from './components/downloadable-files'\n\nexport {\n default as ImageCropper,\n getAspectRatio,\n generateCroppedImageBlob,\n PercentCrop,\n} from './components/ImageCropper'\n\nexport * from './types/form'\n// Former apps package now exported through here\nexport * from './apps'\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ApprovalTypes } from '@oneblink/types';
|
|
2
|
+
/**
|
|
3
|
+
* Inputs a form approval flow instance and set of form submission approvals and
|
|
4
|
+
* returns a flattened array of nodes/steps with inlcuded metadata.
|
|
5
|
+
*
|
|
6
|
+
* #### Example
|
|
7
|
+
*
|
|
8
|
+
* ```js
|
|
9
|
+
* const formApprovalFlowInstance = {
|
|
10
|
+
* steps: [
|
|
11
|
+
* {
|
|
12
|
+
* type: 'STANDARD',
|
|
13
|
+
* group: 'oneblink:administrator',
|
|
14
|
+
* isSkipped: false,
|
|
15
|
+
* label: 'Manager Approval',
|
|
16
|
+
* approvalFormId: '1234',
|
|
17
|
+
* },
|
|
18
|
+
* {
|
|
19
|
+
* type: 'CONCURRENT',
|
|
20
|
+
* nodes: [
|
|
21
|
+
* {
|
|
22
|
+
* label: 'HR Review',
|
|
23
|
+
* group: 'oneblink:administrator',
|
|
24
|
+
* isSkipped: false,
|
|
25
|
+
* approvalFormId: '1234',
|
|
26
|
+
* },
|
|
27
|
+
* {
|
|
28
|
+
* label: 'Finance Review',
|
|
29
|
+
* group: 'oneblink:administrator',
|
|
30
|
+
* isSkipped: false,
|
|
31
|
+
* approvalFormId: '1234',
|
|
32
|
+
* },
|
|
33
|
+
* ],
|
|
34
|
+
* },
|
|
35
|
+
* ],
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* const formSubmissionApprovals = [
|
|
39
|
+
* {
|
|
40
|
+
* id: 'approval1',
|
|
41
|
+
* stepLabel: 'Manager Approval',
|
|
42
|
+
* status: 'APPROVED',
|
|
43
|
+
* createdAt: '2024-03-20T10:00:00Z',
|
|
44
|
+
* updatedAt: '2024-03-20T10:00:00Z',
|
|
45
|
+
* updatedBy: 'fake@fake.com',
|
|
46
|
+
* group: 'oneblink:administrator',
|
|
47
|
+
* approvalFormId: '1234',
|
|
48
|
+
* },
|
|
49
|
+
* ]
|
|
50
|
+
*
|
|
51
|
+
* const nodesWithMeta = getFlowInstanceNodesWithMeta(
|
|
52
|
+
* formApprovalFlowInstance,
|
|
53
|
+
* formSubmissionApprovals,
|
|
54
|
+
* )
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Concurrent nodes are ordered as actioned (by the time they were actioned),
|
|
58
|
+
* then the currently viewed pending approval if
|
|
59
|
+
* `currentlyViewingFormSubmissionApprovalId` is provided, then remaining
|
|
60
|
+
* unactioned nodes in definition order. Sequential steps around a concurrent
|
|
61
|
+
* group are not reordered.
|
|
62
|
+
*
|
|
63
|
+
* @param formApprovalFlowInstance
|
|
64
|
+
* @param formSubmissionApprovals
|
|
65
|
+
* @param currentlyViewingFormSubmissionApprovalId The id of the approval
|
|
66
|
+
* currently being viewed, used to keep that pending concurrent step next to
|
|
67
|
+
* already actioned siblings.
|
|
68
|
+
* @returns ApprovalTypes.FlowInstanceNodeWithMeta[]
|
|
69
|
+
*/
|
|
70
|
+
export declare function getFlowInstanceNodesWithMeta(formApprovalFlowInstance: ApprovalTypes.FormApprovalFlowInstance, formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[], currentlyViewingFormSubmissionApprovalId?: string): ApprovalTypes.FlowInstanceNodeWithMeta[];
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const possibleStatuses = [
|
|
2
|
+
{
|
|
3
|
+
label: 'Awaiting Approval',
|
|
4
|
+
value: 'PENDING',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
label: 'Approved',
|
|
8
|
+
value: 'APPROVED',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
label: 'Clarification Requested',
|
|
12
|
+
value: 'CLARIFICATION_REQUIRED',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: 'Clarification Received',
|
|
16
|
+
value: 'CLARIFICATION_RECEIVED',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'Denied',
|
|
20
|
+
value: 'CLOSED',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const ACTIONED_STATUSES = new Set(['APPROVED', 'CLOSED', 'CLARIFICATION_REQUIRED']);
|
|
24
|
+
function findLatestFormSubmissionApproval(flowInstanceNode, formSubmissionApprovals) {
|
|
25
|
+
return formSubmissionApprovals.reduce((lastUpdatedFormSubmissionApproval, formSubmissionApproval) => {
|
|
26
|
+
if (formSubmissionApproval.stepLabel === flowInstanceNode.label &&
|
|
27
|
+
(!lastUpdatedFormSubmissionApproval ||
|
|
28
|
+
lastUpdatedFormSubmissionApproval.updatedAt <
|
|
29
|
+
formSubmissionApproval.updatedAt)) {
|
|
30
|
+
return formSubmissionApproval;
|
|
31
|
+
}
|
|
32
|
+
return lastUpdatedFormSubmissionApproval;
|
|
33
|
+
}, undefined);
|
|
34
|
+
}
|
|
35
|
+
function getConcurrentSortRank(node, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
36
|
+
const approval = findLatestFormSubmissionApproval(node, formSubmissionApprovals);
|
|
37
|
+
if (approval && ACTIONED_STATUSES.has(approval.status)) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
if (currentlyViewingFormSubmissionApprovalId &&
|
|
41
|
+
(approval === null || approval === void 0 ? void 0 : approval.id) === currentlyViewingFormSubmissionApprovalId) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
function withUpdatedConcurrentFlags(nodes) {
|
|
47
|
+
return nodes.map((node, index) => ({
|
|
48
|
+
...node,
|
|
49
|
+
isConcurrentWithPrevious: index !== 0,
|
|
50
|
+
isConcurrentWithNext: index < nodes.length - 1,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
function sortConcurrentGroup(nodes, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
54
|
+
const sorted = [...nodes].sort((left, right) => {
|
|
55
|
+
var _a, _b, _c, _d;
|
|
56
|
+
const leftRank = getConcurrentSortRank(left, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
57
|
+
const rightRank = getConcurrentSortRank(right, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
58
|
+
if (leftRank !== rightRank) {
|
|
59
|
+
return leftRank - rightRank;
|
|
60
|
+
}
|
|
61
|
+
if (leftRank === 0) {
|
|
62
|
+
const leftUpdatedAt = (_b = (_a = findLatestFormSubmissionApproval(left, formSubmissionApprovals)) === null || _a === void 0 ? void 0 : _a.updatedAt) !== null && _b !== void 0 ? _b : '';
|
|
63
|
+
const rightUpdatedAt = (_d = (_c = findLatestFormSubmissionApproval(right, formSubmissionApprovals)) === null || _c === void 0 ? void 0 : _c.updatedAt) !== null && _d !== void 0 ? _d : '';
|
|
64
|
+
return leftUpdatedAt.localeCompare(rightUpdatedAt);
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
});
|
|
68
|
+
return withUpdatedConcurrentFlags(sorted);
|
|
69
|
+
}
|
|
70
|
+
function sortConcurrentFlowInstanceNodes(nodes, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
71
|
+
const groups = [];
|
|
72
|
+
for (const node of nodes) {
|
|
73
|
+
const currentGroup = groups[groups.length - 1];
|
|
74
|
+
if (currentGroup &&
|
|
75
|
+
currentGroup[0].parentStepLabelsId === node.parentStepLabelsId) {
|
|
76
|
+
currentGroup.push(node);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
groups.push([node]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return groups.flatMap((group) => {
|
|
83
|
+
if (group.length < 2) {
|
|
84
|
+
return group;
|
|
85
|
+
}
|
|
86
|
+
return sortConcurrentGroup(group, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function getFormApprovalFlowInstanceNodes(steps) {
|
|
90
|
+
return steps.reduce((memo, step) => {
|
|
91
|
+
switch (step.type) {
|
|
92
|
+
case 'CONCURRENT': {
|
|
93
|
+
const parentStepLabelsId = step.nodes
|
|
94
|
+
.map(({ label }) => label)
|
|
95
|
+
.join('_');
|
|
96
|
+
for (const node of step.nodes) {
|
|
97
|
+
const index = step.nodes.indexOf(node);
|
|
98
|
+
memo.push({
|
|
99
|
+
...node,
|
|
100
|
+
parentStepLabelsId,
|
|
101
|
+
isConcurrentWithPrevious: index !== 0,
|
|
102
|
+
isConcurrentWithNext: index < step.nodes.length - 1,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'STANDARD':
|
|
108
|
+
default: {
|
|
109
|
+
memo.push({
|
|
110
|
+
...step,
|
|
111
|
+
isConcurrentWithPrevious: false,
|
|
112
|
+
isConcurrentWithNext: false,
|
|
113
|
+
parentStepLabelsId: step.label,
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return memo;
|
|
119
|
+
}, []);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Inputs a form approval flow instance and set of form submission approvals and
|
|
123
|
+
* returns a flattened array of nodes/steps with inlcuded metadata.
|
|
124
|
+
*
|
|
125
|
+
* #### Example
|
|
126
|
+
*
|
|
127
|
+
* ```js
|
|
128
|
+
* const formApprovalFlowInstance = {
|
|
129
|
+
* steps: [
|
|
130
|
+
* {
|
|
131
|
+
* type: 'STANDARD',
|
|
132
|
+
* group: 'oneblink:administrator',
|
|
133
|
+
* isSkipped: false,
|
|
134
|
+
* label: 'Manager Approval',
|
|
135
|
+
* approvalFormId: '1234',
|
|
136
|
+
* },
|
|
137
|
+
* {
|
|
138
|
+
* type: 'CONCURRENT',
|
|
139
|
+
* nodes: [
|
|
140
|
+
* {
|
|
141
|
+
* label: 'HR Review',
|
|
142
|
+
* group: 'oneblink:administrator',
|
|
143
|
+
* isSkipped: false,
|
|
144
|
+
* approvalFormId: '1234',
|
|
145
|
+
* },
|
|
146
|
+
* {
|
|
147
|
+
* label: 'Finance Review',
|
|
148
|
+
* group: 'oneblink:administrator',
|
|
149
|
+
* isSkipped: false,
|
|
150
|
+
* approvalFormId: '1234',
|
|
151
|
+
* },
|
|
152
|
+
* ],
|
|
153
|
+
* },
|
|
154
|
+
* ],
|
|
155
|
+
* }
|
|
156
|
+
*
|
|
157
|
+
* const formSubmissionApprovals = [
|
|
158
|
+
* {
|
|
159
|
+
* id: 'approval1',
|
|
160
|
+
* stepLabel: 'Manager Approval',
|
|
161
|
+
* status: 'APPROVED',
|
|
162
|
+
* createdAt: '2024-03-20T10:00:00Z',
|
|
163
|
+
* updatedAt: '2024-03-20T10:00:00Z',
|
|
164
|
+
* updatedBy: 'fake@fake.com',
|
|
165
|
+
* group: 'oneblink:administrator',
|
|
166
|
+
* approvalFormId: '1234',
|
|
167
|
+
* },
|
|
168
|
+
* ]
|
|
169
|
+
*
|
|
170
|
+
* const nodesWithMeta = getFlowInstanceNodesWithMeta(
|
|
171
|
+
* formApprovalFlowInstance,
|
|
172
|
+
* formSubmissionApprovals,
|
|
173
|
+
* )
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* Concurrent nodes are ordered as actioned (by the time they were actioned),
|
|
177
|
+
* then the currently viewed pending approval if
|
|
178
|
+
* `currentlyViewingFormSubmissionApprovalId` is provided, then remaining
|
|
179
|
+
* unactioned nodes in definition order. Sequential steps around a concurrent
|
|
180
|
+
* group are not reordered.
|
|
181
|
+
*
|
|
182
|
+
* @param formApprovalFlowInstance
|
|
183
|
+
* @param formSubmissionApprovals
|
|
184
|
+
* @param currentlyViewingFormSubmissionApprovalId The id of the approval
|
|
185
|
+
* currently being viewed, used to keep that pending concurrent step next to
|
|
186
|
+
* already actioned siblings.
|
|
187
|
+
* @returns ApprovalTypes.FlowInstanceNodeWithMeta[]
|
|
188
|
+
*/
|
|
189
|
+
export function getFlowInstanceNodesWithMeta(formApprovalFlowInstance, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
190
|
+
const formApprovalFlowInstanceNodes = getFormApprovalFlowInstanceNodes(formApprovalFlowInstance.steps);
|
|
191
|
+
let isDenied = false;
|
|
192
|
+
let isClarificationRequired = false;
|
|
193
|
+
const nodesWithMeta = formApprovalFlowInstanceNodes.map((step) => {
|
|
194
|
+
const associatedApproval = findLatestFormSubmissionApproval(step, formSubmissionApprovals);
|
|
195
|
+
const status = possibleStatuses.find(({ value }) => value === (associatedApproval === null || associatedApproval === void 0 ? void 0 : associatedApproval.status));
|
|
196
|
+
let isDeniedInConcurrentStep = false;
|
|
197
|
+
let isClarificationRequiredInConcurrentStep = false;
|
|
198
|
+
if ((status === null || status === void 0 ? void 0 : status.value) === 'PENDING') {
|
|
199
|
+
const siblingNodes = formApprovalFlowInstanceNodes.filter(({ parentStepLabelsId, label }) => parentStepLabelsId === step.parentStepLabelsId &&
|
|
200
|
+
label !== step.label);
|
|
201
|
+
for (const siblingNode of siblingNodes) {
|
|
202
|
+
const formSubmissionApproval = findLatestFormSubmissionApproval(siblingNode, formSubmissionApprovals);
|
|
203
|
+
if ((formSubmissionApproval === null || formSubmissionApproval === void 0 ? void 0 : formSubmissionApproval.status) === 'CLOSED') {
|
|
204
|
+
isDeniedInConcurrentStep = true;
|
|
205
|
+
}
|
|
206
|
+
if ((formSubmissionApproval === null || formSubmissionApproval === void 0 ? void 0 : formSubmissionApproval.status) === 'CLARIFICATION_REQUIRED') {
|
|
207
|
+
isClarificationRequiredInConcurrentStep = true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const isAfterDeniedStep = isDenied;
|
|
212
|
+
if ((status === null || status === void 0 ? void 0 : status.value) === 'CLOSED') {
|
|
213
|
+
isDenied = true;
|
|
214
|
+
}
|
|
215
|
+
const isAfterClarificationRequiredStep = isClarificationRequired;
|
|
216
|
+
if ((status === null || status === void 0 ? void 0 : status.value) === 'CLARIFICATION_REQUIRED') {
|
|
217
|
+
isClarificationRequired = true;
|
|
218
|
+
}
|
|
219
|
+
let description = 'Awaiting previous approvals';
|
|
220
|
+
if (step.isSkipped) {
|
|
221
|
+
description = 'Skipped based on form submission';
|
|
222
|
+
}
|
|
223
|
+
else if (status) {
|
|
224
|
+
const updatedByText = (associatedApproval === null || associatedApproval === void 0 ? void 0 : associatedApproval.updatedBy)
|
|
225
|
+
? ` by ${associatedApproval.updatedBy}`
|
|
226
|
+
: '';
|
|
227
|
+
description = `${status.label}${updatedByText}`;
|
|
228
|
+
}
|
|
229
|
+
else if (isAfterDeniedStep) {
|
|
230
|
+
description = 'Denied in a previous step';
|
|
231
|
+
}
|
|
232
|
+
else if (isAfterClarificationRequiredStep) {
|
|
233
|
+
description = 'Sent for clarification in a previous step';
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
...step,
|
|
237
|
+
status,
|
|
238
|
+
isDeniedInConcurrentStep,
|
|
239
|
+
isAfterDeniedStep,
|
|
240
|
+
isClarificationRequiredInConcurrentStep,
|
|
241
|
+
isAfterClarificationRequiredStep,
|
|
242
|
+
description,
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
return sortConcurrentFlowInstanceNodes(nodesWithMeta, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=getFlowInstanceNodesWithMeta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getFlowInstanceNodesWithMeta.js","sourceRoot":"","sources":["../../src/services/getFlowInstanceNodesWithMeta.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApprovalTypes } from '@oneblink/types'\n\nconst possibleStatuses = [\n {\n label: 'Awaiting Approval',\n value: 'PENDING',\n },\n {\n label: 'Approved',\n value: 'APPROVED',\n },\n {\n label: 'Clarification Requested',\n value: 'CLARIFICATION_REQUIRED',\n },\n {\n label: 'Clarification Received',\n value: 'CLARIFICATION_RECEIVED',\n },\n {\n label: 'Denied',\n value: 'CLOSED',\n },\n]\n\nconst ACTIONED_STATUSES: ReadonlySet<\n ApprovalTypes.FormSubmissionApproval['status']\n> = new Set(['APPROVED', 'CLOSED', 'CLARIFICATION_REQUIRED'])\n\nfunction findLatestFormSubmissionApproval(\n flowInstanceNode: { label: string },\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n) {\n return formSubmissionApprovals.reduce<\n ApprovalTypes.FormSubmissionApproval | undefined\n >((lastUpdatedFormSubmissionApproval, formSubmissionApproval) => {\n if (\n formSubmissionApproval.stepLabel === flowInstanceNode.label &&\n (!lastUpdatedFormSubmissionApproval ||\n lastUpdatedFormSubmissionApproval.updatedAt <\n formSubmissionApproval.updatedAt)\n ) {\n return formSubmissionApproval\n }\n return lastUpdatedFormSubmissionApproval\n }, undefined)\n}\n\nfunction getConcurrentSortRank(\n node: ApprovalTypes.FlowInstanceNodeWithMeta,\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId: string | undefined,\n) {\n const approval = findLatestFormSubmissionApproval(\n node,\n formSubmissionApprovals,\n )\n if (approval && ACTIONED_STATUSES.has(approval.status)) {\n return 0\n }\n if (\n currentlyViewingFormSubmissionApprovalId &&\n approval?.id === currentlyViewingFormSubmissionApprovalId\n ) {\n return 1\n }\n return 2\n}\n\nfunction withUpdatedConcurrentFlags(\n nodes: ApprovalTypes.FlowInstanceNodeWithMeta[],\n) {\n return nodes.map((node, index) => ({\n ...node,\n isConcurrentWithPrevious: index !== 0,\n isConcurrentWithNext: index < nodes.length - 1,\n }))\n}\n\nfunction sortConcurrentGroup(\n nodes: ApprovalTypes.FlowInstanceNodeWithMeta[],\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId: string | undefined,\n) {\n const sorted = [...nodes].sort((left, right) => {\n const leftRank = getConcurrentSortRank(\n left,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n const rightRank = getConcurrentSortRank(\n right,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n if (leftRank !== rightRank) {\n return leftRank - rightRank\n }\n if (leftRank === 0) {\n const leftUpdatedAt =\n findLatestFormSubmissionApproval(left, formSubmissionApprovals)\n ?.updatedAt ?? ''\n const rightUpdatedAt =\n findLatestFormSubmissionApproval(right, formSubmissionApprovals)\n ?.updatedAt ?? ''\n return leftUpdatedAt.localeCompare(rightUpdatedAt)\n }\n return 0\n })\n return withUpdatedConcurrentFlags(sorted)\n}\n\nfunction sortConcurrentFlowInstanceNodes(\n nodes: ApprovalTypes.FlowInstanceNodeWithMeta[],\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId: string | undefined,\n) {\n const groups: ApprovalTypes.FlowInstanceNodeWithMeta[][] = []\n for (const node of nodes) {\n const currentGroup = groups[groups.length - 1]\n if (\n currentGroup &&\n currentGroup[0].parentStepLabelsId === node.parentStepLabelsId\n ) {\n currentGroup.push(node)\n } else {\n groups.push([node])\n }\n }\n\n return groups.flatMap((group) => {\n if (group.length < 2) {\n return group\n }\n return sortConcurrentGroup(\n group,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n })\n}\n\nfunction getFormApprovalFlowInstanceNodes(\n steps: ApprovalTypes.FormApprovalFlowInstanceStep[],\n) {\n return steps.reduce<\n Array<\n ApprovalTypes.FormApprovalFlowInstanceNode &\n Pick<\n ApprovalTypes.FlowInstanceNodeMeta,\n | 'parentStepLabelsId'\n | 'isConcurrentWithPrevious'\n | 'isConcurrentWithNext'\n >\n >\n >((memo, step) => {\n switch (step.type) {\n case 'CONCURRENT': {\n const parentStepLabelsId = step.nodes\n .map(({ label }) => label)\n .join('_')\n for (const node of step.nodes) {\n const index = step.nodes.indexOf(node)\n memo.push({\n ...node,\n parentStepLabelsId,\n isConcurrentWithPrevious: index !== 0,\n isConcurrentWithNext: index < step.nodes.length - 1,\n })\n }\n break\n }\n case 'STANDARD':\n default: {\n memo.push({\n ...step,\n isConcurrentWithPrevious: false,\n isConcurrentWithNext: false,\n parentStepLabelsId: step.label,\n })\n break\n }\n }\n return memo\n }, [])\n}\n\n/**\n * Inputs a form approval flow instance and set of form submission approvals and\n * returns a flattened array of nodes/steps with inlcuded metadata.\n *\n * #### Example\n *\n * ```js\n * const formApprovalFlowInstance = {\n * steps: [\n * {\n * type: 'STANDARD',\n * group: 'oneblink:administrator',\n * isSkipped: false,\n * label: 'Manager Approval',\n * approvalFormId: '1234',\n * },\n * {\n * type: 'CONCURRENT',\n * nodes: [\n * {\n * label: 'HR Review',\n * group: 'oneblink:administrator',\n * isSkipped: false,\n * approvalFormId: '1234',\n * },\n * {\n * label: 'Finance Review',\n * group: 'oneblink:administrator',\n * isSkipped: false,\n * approvalFormId: '1234',\n * },\n * ],\n * },\n * ],\n * }\n *\n * const formSubmissionApprovals = [\n * {\n * id: 'approval1',\n * stepLabel: 'Manager Approval',\n * status: 'APPROVED',\n * createdAt: '2024-03-20T10:00:00Z',\n * updatedAt: '2024-03-20T10:00:00Z',\n * updatedBy: 'fake@fake.com',\n * group: 'oneblink:administrator',\n * approvalFormId: '1234',\n * },\n * ]\n *\n * const nodesWithMeta = getFlowInstanceNodesWithMeta(\n * formApprovalFlowInstance,\n * formSubmissionApprovals,\n * )\n * ```\n *\n * Concurrent nodes are ordered as actioned (by the time they were actioned),\n * then the currently viewed pending approval if\n * `currentlyViewingFormSubmissionApprovalId` is provided, then remaining\n * unactioned nodes in definition order. Sequential steps around a concurrent\n * group are not reordered.\n *\n * @param formApprovalFlowInstance\n * @param formSubmissionApprovals\n * @param currentlyViewingFormSubmissionApprovalId The id of the approval\n * currently being viewed, used to keep that pending concurrent step next to\n * already actioned siblings.\n * @returns ApprovalTypes.FlowInstanceNodeWithMeta[]\n */\nexport function getFlowInstanceNodesWithMeta(\n formApprovalFlowInstance: ApprovalTypes.FormApprovalFlowInstance,\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId?: string,\n): ApprovalTypes.FlowInstanceNodeWithMeta[] {\n const formApprovalFlowInstanceNodes = getFormApprovalFlowInstanceNodes(\n formApprovalFlowInstance.steps,\n )\n let isDenied = false\n let isClarificationRequired = false\n const nodesWithMeta = formApprovalFlowInstanceNodes.map((step) => {\n const associatedApproval = findLatestFormSubmissionApproval(\n step,\n formSubmissionApprovals,\n )\n const status = possibleStatuses.find(\n ({ value }) => value === associatedApproval?.status,\n )\n\n let isDeniedInConcurrentStep = false\n let isClarificationRequiredInConcurrentStep = false\n if (status?.value === 'PENDING') {\n const siblingNodes = formApprovalFlowInstanceNodes.filter(\n ({ parentStepLabelsId, label }) =>\n parentStepLabelsId === step.parentStepLabelsId &&\n label !== step.label,\n )\n for (const siblingNode of siblingNodes) {\n const formSubmissionApproval = findLatestFormSubmissionApproval(\n siblingNode,\n formSubmissionApprovals,\n )\n if (formSubmissionApproval?.status === 'CLOSED') {\n isDeniedInConcurrentStep = true\n }\n if (formSubmissionApproval?.status === 'CLARIFICATION_REQUIRED') {\n isClarificationRequiredInConcurrentStep = true\n }\n }\n }\n\n const isAfterDeniedStep = isDenied\n if (status?.value === 'CLOSED') {\n isDenied = true\n }\n\n const isAfterClarificationRequiredStep = isClarificationRequired\n if (status?.value === 'CLARIFICATION_REQUIRED') {\n isClarificationRequired = true\n }\n\n let description = 'Awaiting previous approvals'\n if (step.isSkipped) {\n description = 'Skipped based on form submission'\n } else if (status) {\n const updatedByText = associatedApproval?.updatedBy\n ? ` by ${associatedApproval.updatedBy}`\n : ''\n description = `${status.label}${updatedByText}`\n } else if (isAfterDeniedStep) {\n description = 'Denied in a previous step'\n } else if (isAfterClarificationRequiredStep) {\n description = 'Sent for clarification in a previous step'\n }\n\n return {\n ...step,\n status,\n isDeniedInConcurrentStep,\n isAfterDeniedStep,\n isClarificationRequiredInConcurrentStep,\n isAfterClarificationRequiredStep,\n description,\n }\n })\n\n return sortConcurrentFlowInstanceNodes(\n nodesWithMeta,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneblink/apps-react",
|
|
3
3
|
"description": "Helper functions for OneBlink apps in ReactJS.",
|
|
4
|
-
"version": "13.0.0-beta.
|
|
4
|
+
"version": "13.0.0-beta.11",
|
|
5
5
|
"author": "OneBlink <developers@oneblink.io> (https://oneblink.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/oneblink/apps-react/issues"
|