From be38607698b12309af5b79259afbbf037e7027bc Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Wed, 21 Dec 2022 09:44:00 +0100 Subject: [PATCH 01/10] feat: ideas for plugin wrappers [LIBS-397] --- runtime/package.json | 8 ++++-- runtime/src/PluginSender.tsx | 53 +++++++++++++++++++++++++++++++++++ runtime/src/PluginWrapper.tsx | 43 ++++++++++++++++++++++++++++ runtime/src/index.ts | 4 +++ yarn.lock | 49 ++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 runtime/src/PluginSender.tsx create mode 100644 runtime/src/PluginWrapper.tsx diff --git a/runtime/package.json b/runtime/package.json index 5be1ff035..abbda9628 100644 --- a/runtime/package.json +++ b/runtime/package.json @@ -23,10 +23,11 @@ "build/**" ], "dependencies": { + "@dhis2/app-service-alerts": "3.7.0", "@dhis2/app-service-config": "3.7.0", "@dhis2/app-service-data": "3.7.0", - "@dhis2/app-service-alerts": "3.7.0", - "@dhis2/app-service-offline": "3.7.0" + "@dhis2/app-service-offline": "3.7.0", + "post-robot": "^10.0.46" }, "peerDependencies": { "prop-types": "^15.7.2", @@ -38,5 +39,8 @@ "build:package": "d2-app-scripts build", "build": "concurrently -n build,types \"yarn build:package\" \"yarn build:types\"", "test": "echo \"No tests yet!\"" + }, + "devDependencies": { + "@types/post-robot": "^10.0.3" } } diff --git a/runtime/src/PluginSender.tsx b/runtime/src/PluginSender.tsx new file mode 100644 index 000000000..3e16bcd6b --- /dev/null +++ b/runtime/src/PluginSender.tsx @@ -0,0 +1,53 @@ +import postRobot from 'post-robot' +import React, { useEffect, useRef, useState } from 'react' + +export const PluginSender = ({ + pluginSource, + ...propsToPass +}: { + pluginSource: string + propsToPass: any +}): JSX.Element => { + const iframeRef = useRef() + + const [missingProps, setMissingProps] = useState(null) + + useEffect(() => { + if (iframeRef?.current) { + const iframeProps = { ...propsToPass, setMissingProps } + const listener = postRobot.on( + 'getPropsFromParent', + // listen for messages coming only from the iframe rendered by this component + { window: iframeRef.current.contentWindow }, + (): any => { + return iframeProps + } + ) + + return () => listener.cancel() + } + }, [propsToPass]) + + if (missingProps && missingProps?.length > 0) { + return ( +

{`Plugin could not load because required props are missing: ${missingProps.join()}`}

+ ) + } + + return ( +
+ {pluginSource ? ( + + ) : null} +
+ ) +} diff --git a/runtime/src/PluginWrapper.tsx b/runtime/src/PluginWrapper.tsx new file mode 100644 index 000000000..713c44296 --- /dev/null +++ b/runtime/src/PluginWrapper.tsx @@ -0,0 +1,43 @@ +import postRobot from 'post-robot' +import { useEffect, useState } from 'react' + +export const PluginWrapper = ({ + requiredProps, + children, +}: { + requiredProps: [string] + children: any +}): any => { + const [propsFromParent, setPropsFromParent] = useState() + + const receivePropsFromParent = (event: any) => { + const { data: receivedProps } = event + const { setMissingProps, ...explictlyPassedProps } = receivedProps + + setPropsFromParent(explictlyPassedProps) + + // check for required props + const missingProps = requiredProps?.filter( + (prop) => !receivedProps[prop] + ) + if (missingProps && missingProps.length > 0) { + setMissingProps(missingProps) + console.error( + `The following required props were not provided: ${missingProps.join( + ',' + )}` + ) + } else { + setMissingProps([]) + } + } + + useEffect(() => { + postRobot + .send(window.top, 'getPropsFromParent') + .then(receivePropsFromParent) + .catch((err: Error) => console.error(err)) + }) + + return children({ ...propsFromParent }) +} diff --git a/runtime/src/index.ts b/runtime/src/index.ts index a3f580d98..06048f496 100644 --- a/runtime/src/index.ts +++ b/runtime/src/index.ts @@ -22,4 +22,8 @@ export { clearSensitiveCaches, } from '@dhis2/app-service-offline' +export { PluginSender } from './PluginSender' + +export { PluginWrapper } from './PluginWrapper' + export { Provider } from './Provider' diff --git a/yarn.lock b/yarn.lock index 3a51d5723..a3b53d947 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2440,6 +2440,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/post-robot@^10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@types/post-robot/-/post-robot-10.0.3.tgz#d1429085f2faf4c87f841dab4e51472457edbf31" + integrity sha512-y8ysuxddaG8V/oA1Ay6Err7nSADRa9Bv1rl0ZQpJ0qgdIQ7ks3CHcOsYL4qE8w75+/XYDS94dBeXDs0xexm3tA== + "@types/prettier@^2.0.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd" @@ -3805,6 +3810,15 @@ bcryptjs@^2.3.0: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= +belter@^1.0.41: + version "1.0.190" + resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" + integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== + dependencies: + cross-domain-safe-weakmap "^1" + cross-domain-utils "^2" + zalgo-promise "^1" + bfj@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" @@ -5011,6 +5025,20 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: + version "1.0.29" + resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" + integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== + dependencies: + cross-domain-utils "^2.0.0" + +cross-domain-utils@^2, cross-domain-utils@^2.0.0: + version "2.0.38" + resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" + integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== + dependencies: + zalgo-promise "^1.0.11" + cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -11503,6 +11531,17 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +post-robot@^10.0.46: + version "10.0.46" + resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" + integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== + dependencies: + belter "^1.0.41" + cross-domain-safe-weakmap "^1.0.1" + cross-domain-utils "^2.0.0" + universal-serialize "^1.0.4" + zalgo-promise "^1.0.3" + postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -15086,6 +15125,11 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +universal-serialize@^1.0.4: + version "1.0.10" + resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" + integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -16065,6 +16109,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: + version "1.0.48" + resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" + integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== + zip-stream@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" From 30c963c112b2865ae824c7e3ce06279ed374983c Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Mon, 23 Jan 2023 14:05:33 +0100 Subject: [PATCH 02/10] feat: update plugin wrappers --- examples/cra/package.json | 3 +- examples/cra/yarn.lock | 94 ++++++++++++++++--- examples/query-playground/package.json | 3 +- examples/query-playground/yarn.lock | 96 ++++++++++++++++--- runtime/package.json | 2 + runtime/src/PluginSender.tsx | 53 ----------- runtime/src/PluginWrapper.tsx | 43 --------- runtime/src/index.ts | 4 +- services/plugin/.gitignore | 5 + services/plugin/README.md | 11 +++ services/plugin/d2.config.js | 9 ++ services/plugin/jest.config.js | 10 ++ services/plugin/package.json | 41 ++++++++ services/plugin/src/PluginError.tsx | 58 ++++++++++++ services/plugin/src/PluginSender.tsx | 125 +++++++++++++++++++++++++ services/plugin/src/PluginWrapper.tsx | 69 ++++++++++++++ services/plugin/src/index.ts | 3 + services/plugin/src/setupRTL.ts | 5 + services/plugin/src/types.ts | 1 + services/plugin/tsconfig.json | 10 ++ 20 files changed, 517 insertions(+), 128 deletions(-) delete mode 100644 runtime/src/PluginSender.tsx delete mode 100644 runtime/src/PluginWrapper.tsx create mode 100644 services/plugin/.gitignore create mode 100644 services/plugin/README.md create mode 100644 services/plugin/d2.config.js create mode 100644 services/plugin/jest.config.js create mode 100644 services/plugin/package.json create mode 100644 services/plugin/src/PluginError.tsx create mode 100644 services/plugin/src/PluginSender.tsx create mode 100644 services/plugin/src/PluginWrapper.tsx create mode 100644 services/plugin/src/index.ts create mode 100644 services/plugin/src/setupRTL.ts create mode 100644 services/plugin/src/types.ts create mode 100644 services/plugin/tsconfig.json diff --git a/examples/cra/package.json b/examples/cra/package.json index 4553c9729..e7eec2caf 100644 --- a/examples/cra/package.json +++ b/examples/cra/package.json @@ -16,7 +16,8 @@ "@dhis2/app-service-alerts": "file:../../services/alerts", "@dhis2/app-service-config": "file:../../services/config", "@dhis2/app-service-data": "file:../../services/data", - "@dhis2/app-service-offline": "file:../../services/offline" + "@dhis2/app-service-offline": "file:../../services/offline", + "@dhis2/app-service-plugin": "file:../../services/plugin" }, "scripts": { "start": "react-scripts start", diff --git a/examples/cra/yarn.lock b/examples/cra/yarn.lock index 1db982bee..ae729ea73 100644 --- a/examples/cra/yarn.lock +++ b/examples/cra/yarn.lock @@ -1054,29 +1054,43 @@ integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== "@dhis2/app-runtime@file:../../runtime": - version "3.5.0" + version "3.7.0" dependencies: - "@dhis2/app-service-alerts" "3.5.0" - "@dhis2/app-service-config" "3.5.0" - "@dhis2/app-service-data" "3.5.0" - "@dhis2/app-service-offline" "3.5.0" + "@dhis2/app-service-alerts" "3.7.0" + "@dhis2/app-service-config" "3.7.0" + "@dhis2/app-service-data" "3.7.0" + "@dhis2/app-service-offline" "3.7.0" + "@dhis2/app-service-plugin" "3.7.0" + "@dhis2/d2-i18n" "^1.1.0" + post-robot "^10.0.46" -"@dhis2/app-service-alerts@3.5.0", "@dhis2/app-service-alerts@file:../../services/alerts": - version "3.5.0" +"@dhis2/app-service-alerts@3.7.0", "@dhis2/app-service-alerts@file:../../services/alerts": + version "3.7.0" -"@dhis2/app-service-config@3.5.0", "@dhis2/app-service-config@file:../../services/config": - version "3.5.0" +"@dhis2/app-service-config@3.7.0", "@dhis2/app-service-config@file:../../services/config": + version "3.7.0" -"@dhis2/app-service-data@3.5.0", "@dhis2/app-service-data@file:../../services/data": - version "3.5.0" +"@dhis2/app-service-data@3.7.0", "@dhis2/app-service-data@file:../../services/data": + version "3.7.0" dependencies: react-query "^3.13.11" -"@dhis2/app-service-offline@3.5.0", "@dhis2/app-service-offline@file:../../services/offline": - version "3.5.0" +"@dhis2/app-service-offline@3.7.0", "@dhis2/app-service-offline@file:../../services/offline": + version "3.7.0" dependencies: lodash "^4.17.21" +"@dhis2/app-service-plugin@3.7.0", "@dhis2/app-service-plugin@file:../../services/plugin": + version "3.7.0" + +"@dhis2/d2-i18n@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@dhis2/d2-i18n/-/d2-i18n-1.1.0.tgz#ec777c5091f747e4c5aa4f9801c62ba4d1ef3d16" + integrity sha512-x3u58goDQsMfBzy50koxNrJjofJTtjRZOfz6f6Py/wMMJfp/T6vZjWMQgcfWH0JrV6d04K1RTt6bI05wqsVQvg== + dependencies: + i18next "^10.3" + moment "^2.24.0" + "@hapi/address@2.x.x": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -2259,6 +2273,15 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +belter@^1.0.41: + version "1.0.190" + resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" + integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== + dependencies: + cross-domain-safe-weakmap "^1" + cross-domain-utils "^2" + zalgo-promise "^1" + big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -3143,6 +3166,20 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: + version "1.0.29" + resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" + integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== + dependencies: + cross-domain-utils "^2.0.0" + +cross-domain-utils@^2, cross-domain-utils@^2.0.0: + version "2.0.38" + resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" + integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== + dependencies: + zalgo-promise "^1.0.11" + cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -5149,6 +5186,11 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +i18next@^10.3: + version "10.6.0" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-10.6.0.tgz#90ffd9f9bc617f34b9a12e037260f524445f7684" + integrity sha512-ycRlN145kQf8EsyDAzMfjqv1ZT1Jwp7P2H/07bP8JLWm+7cSLD4XqlJOvq4mKVS2y2mMIy10lX9ZeYUdQ0qSRw== + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -6878,6 +6920,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" +moment@^2.24.0: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -7718,6 +7765,17 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +post-robot@^10.0.46: + version "10.0.46" + resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" + integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== + dependencies: + belter "^1.0.41" + cross-domain-safe-weakmap "^1.0.1" + cross-domain-utils "^2.0.0" + universal-serialize "^1.0.4" + zalgo-promise "^1.0.3" + postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -10326,6 +10384,11 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +universal-serialize@^1.0.4: + version "1.0.10" + resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" + integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -11043,3 +11106,8 @@ yargs@^15.3.1: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^18.1.1" + +zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: + version "1.0.48" + resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" + integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== diff --git a/examples/query-playground/package.json b/examples/query-playground/package.json index ccfa94237..60a58f94b 100644 --- a/examples/query-playground/package.json +++ b/examples/query-playground/package.json @@ -29,6 +29,7 @@ "@dhis2/app-service-alerts": "file:../../services/alerts", "@dhis2/app-service-config": "file:../../services/config", "@dhis2/app-service-data": "file:../../services/data", - "@dhis2/app-service-offline": "file:../../services/offline" + "@dhis2/app-service-offline": "file:../../services/offline", + "@dhis2/app-service-plugin": "file:../../services/plugin" } } diff --git a/examples/query-playground/yarn.lock b/examples/query-playground/yarn.lock index 9ac5d7f62..38258da7f 100644 --- a/examples/query-playground/yarn.lock +++ b/examples/query-playground/yarn.lock @@ -1789,30 +1789,46 @@ dependencies: moment "^2.24.0" -"@dhis2/app-runtime@*", "@dhis2/app-runtime@^2.2.2", "@dhis2/app-runtime@file:../../runtime": - version "3.5.0" +"@dhis2/app-runtime@*": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.8.0.tgz#4ec7fc4ec6647dc8428e3c0d2e14b2d188a993b9" + integrity sha512-f5M1RfUJb4yZaPDywTfogVXjzWcuYGJ7JQzny6iWXrJu1+qrRKYbfFutYNhB+4bXD4bB59DelHWqVaHtrGvbVA== dependencies: - "@dhis2/app-service-alerts" "3.5.0" - "@dhis2/app-service-config" "3.5.0" - "@dhis2/app-service-data" "3.5.0" - "@dhis2/app-service-offline" "3.5.0" + "@dhis2/app-service-alerts" "3.8.0" + "@dhis2/app-service-config" "3.8.0" + "@dhis2/app-service-data" "3.8.0" + "@dhis2/app-service-offline" "3.8.0" -"@dhis2/app-service-alerts@3.5.0", "@dhis2/app-service-alerts@file:../../services/alerts": - version "3.5.0" +"@dhis2/app-runtime@^2.2.2", "@dhis2/app-runtime@file:../../runtime": + version "3.7.0" + dependencies: + "@dhis2/app-service-alerts" "3.7.0" + "@dhis2/app-service-config" "3.7.0" + "@dhis2/app-service-data" "3.7.0" + "@dhis2/app-service-offline" "3.7.0" + "@dhis2/app-service-plugin" "3.7.0" + "@dhis2/d2-i18n" "^1.1.0" + post-robot "^10.0.46" -"@dhis2/app-service-config@3.5.0", "@dhis2/app-service-config@file:../../services/config": - version "3.5.0" +"@dhis2/app-service-alerts@3.7.0", "@dhis2/app-service-alerts@3.8.0", "@dhis2/app-service-alerts@file:../../services/alerts": + version "3.7.0" -"@dhis2/app-service-data@3.5.0", "@dhis2/app-service-data@file:../../services/data": - version "3.5.0" +"@dhis2/app-service-config@3.7.0", "@dhis2/app-service-config@3.8.0", "@dhis2/app-service-config@file:../../services/config": + version "3.7.0" + +"@dhis2/app-service-data@3.7.0", "@dhis2/app-service-data@3.8.0", "@dhis2/app-service-data@file:../../services/data": + version "3.7.0" dependencies: react-query "^3.13.11" -"@dhis2/app-service-offline@3.5.0", "@dhis2/app-service-offline@file:../../services/offline": - version "3.5.0" +"@dhis2/app-service-offline@3.7.0", "@dhis2/app-service-offline@3.8.0", "@dhis2/app-service-offline@file:../../services/offline": + version "3.7.0" dependencies: lodash "^4.17.21" +"@dhis2/app-service-plugin@3.7.0", "@dhis2/app-service-plugin@file:../../services/plugin": + version "3.7.0" + "@dhis2/app-shell@5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@dhis2/app-shell/-/app-shell-5.2.0.tgz#19fc3c6b18ea18048d3cdd1680ce535417edb6b3" @@ -1900,6 +1916,14 @@ i18next "^10.3" moment "^2.24.0" +"@dhis2/d2-i18n@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@dhis2/d2-i18n/-/d2-i18n-1.1.0.tgz#ec777c5091f747e4c5aa4f9801c62ba4d1ef3d16" + integrity sha512-x3u58goDQsMfBzy50koxNrJjofJTtjRZOfz6f6Py/wMMJfp/T6vZjWMQgcfWH0JrV6d04K1RTt6bI05wqsVQvg== + dependencies: + i18next "^10.3" + moment "^2.24.0" + "@dhis2/prop-types@^1.6.4": version "1.6.4" resolved "https://registry.yarnpkg.com/@dhis2/prop-types/-/prop-types-1.6.4.tgz#ec4d256c9440d4d00071524422a727c61ddaa6f6" @@ -3359,6 +3383,15 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +belter@^1.0.41: + version "1.0.190" + resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" + integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== + dependencies: + cross-domain-safe-weakmap "^1" + cross-domain-utils "^2" + zalgo-promise "^1" + big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -4422,6 +4455,20 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: + version "1.0.29" + resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" + integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== + dependencies: + cross-domain-utils "^2.0.0" + +cross-domain-utils@^2, cross-domain-utils@^2.0.0: + version "2.0.38" + resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" + integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== + dependencies: + zalgo-promise "^1.0.11" + cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -9611,6 +9658,17 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +post-robot@^10.0.46: + version "10.0.46" + resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" + integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== + dependencies: + belter "^1.0.41" + cross-domain-safe-weakmap "^1.0.1" + cross-domain-utils "^2.0.0" + universal-serialize "^1.0.4" + zalgo-promise "^1.0.3" + postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -12665,6 +12723,11 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +universal-serialize@^1.0.4: + version "1.0.10" + resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" + integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -13499,6 +13562,11 @@ yargs@^15.0.0, yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.1" +zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: + version "1.0.48" + resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" + integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== + zip-stream@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" diff --git a/runtime/package.json b/runtime/package.json index abbda9628..d5f718ce4 100644 --- a/runtime/package.json +++ b/runtime/package.json @@ -27,6 +27,8 @@ "@dhis2/app-service-config": "3.7.0", "@dhis2/app-service-data": "3.7.0", "@dhis2/app-service-offline": "3.7.0", + "@dhis2/app-service-plugin": "3.7.0", + "@dhis2/d2-i18n": "^1.1.0", "post-robot": "^10.0.46" }, "peerDependencies": { diff --git a/runtime/src/PluginSender.tsx b/runtime/src/PluginSender.tsx deleted file mode 100644 index 3e16bcd6b..000000000 --- a/runtime/src/PluginSender.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import postRobot from 'post-robot' -import React, { useEffect, useRef, useState } from 'react' - -export const PluginSender = ({ - pluginSource, - ...propsToPass -}: { - pluginSource: string - propsToPass: any -}): JSX.Element => { - const iframeRef = useRef() - - const [missingProps, setMissingProps] = useState(null) - - useEffect(() => { - if (iframeRef?.current) { - const iframeProps = { ...propsToPass, setMissingProps } - const listener = postRobot.on( - 'getPropsFromParent', - // listen for messages coming only from the iframe rendered by this component - { window: iframeRef.current.contentWindow }, - (): any => { - return iframeProps - } - ) - - return () => listener.cancel() - } - }, [propsToPass]) - - if (missingProps && missingProps?.length > 0) { - return ( -

{`Plugin could not load because required props are missing: ${missingProps.join()}`}

- ) - } - - return ( -
- {pluginSource ? ( - - ) : null} -
- ) -} diff --git a/runtime/src/PluginWrapper.tsx b/runtime/src/PluginWrapper.tsx deleted file mode 100644 index 713c44296..000000000 --- a/runtime/src/PluginWrapper.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import postRobot from 'post-robot' -import { useEffect, useState } from 'react' - -export const PluginWrapper = ({ - requiredProps, - children, -}: { - requiredProps: [string] - children: any -}): any => { - const [propsFromParent, setPropsFromParent] = useState() - - const receivePropsFromParent = (event: any) => { - const { data: receivedProps } = event - const { setMissingProps, ...explictlyPassedProps } = receivedProps - - setPropsFromParent(explictlyPassedProps) - - // check for required props - const missingProps = requiredProps?.filter( - (prop) => !receivedProps[prop] - ) - if (missingProps && missingProps.length > 0) { - setMissingProps(missingProps) - console.error( - `The following required props were not provided: ${missingProps.join( - ',' - )}` - ) - } else { - setMissingProps([]) - } - } - - useEffect(() => { - postRobot - .send(window.top, 'getPropsFromParent') - .then(receivePropsFromParent) - .catch((err: Error) => console.error(err)) - }) - - return children({ ...propsFromParent }) -} diff --git a/runtime/src/index.ts b/runtime/src/index.ts index 06048f496..3339b4d45 100644 --- a/runtime/src/index.ts +++ b/runtime/src/index.ts @@ -22,8 +22,6 @@ export { clearSensitiveCaches, } from '@dhis2/app-service-offline' -export { PluginSender } from './PluginSender' - -export { PluginWrapper } from './PluginWrapper' +export { PluginSender, PluginWrapper } from '@dhis2/app-service-plugin' export { Provider } from './Provider' diff --git a/services/plugin/.gitignore b/services/plugin/.gitignore new file mode 100644 index 000000000..6570aa5cb --- /dev/null +++ b/services/plugin/.gitignore @@ -0,0 +1,5 @@ +# DHIS2 Platform +node_modules +.d2 +src/locales +build \ No newline at end of file diff --git a/services/plugin/README.md b/services/plugin/README.md new file mode 100644 index 000000000..589924263 --- /dev/null +++ b/services/plugin/README.md @@ -0,0 +1,11 @@ +# DHIS2 App Data Service + +Application configuration for [DHIS2](https://dhis2.org) applications + +This library is intended for use with the [DHIS2 Application Platform](https://github.com/dhis2/app-platform). + +## Installation + +This package is internal to `@dhis2/app-runtime` and generally should not be installed independently. + +See [the docs](https://runtime.dhis2.nu) for more. diff --git a/services/plugin/d2.config.js b/services/plugin/d2.config.js new file mode 100644 index 000000000..84bec20f1 --- /dev/null +++ b/services/plugin/d2.config.js @@ -0,0 +1,9 @@ +const config = { + type: 'lib', + + entryPoints: { + lib: './src/index.ts', + }, +} + +module.exports = config diff --git a/services/plugin/jest.config.js b/services/plugin/jest.config.js new file mode 100644 index 000000000..66cd80913 --- /dev/null +++ b/services/plugin/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + collectCoverageFrom: [ + 'src/**/*.(js|jsx|ts|tsx)', + '!src/index.ts', + '!src/types.ts', + ], + + // Setup react-testing-library + setupFilesAfterEnv: ['/src/setupRTL.ts'], +} diff --git a/services/plugin/package.json b/services/plugin/package.json new file mode 100644 index 000000000..ec3830cff --- /dev/null +++ b/services/plugin/package.json @@ -0,0 +1,41 @@ +{ + "name": "@dhis2/app-service-plugin", + "version": "3.7.0", + "main": "./build/cjs/index.js", + "module": "./build/es/index.js", + "types": "build/types/index.d.ts", + "exports": { + "import": "./build/es/index.js", + "require": "./build/cjs/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/dhis2/app-runtime.git", + "directory": "services/plugin" + }, + "author": "Austin McGee ", + "license": "BSD-3-Clause", + "publishConfig": { + "access": "public" + }, + "files": [ + "build/**" + ], + "peerDependencies": { + "prop-types": "^15.7.2", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "@dhis2/app-service-data": "3.7.0" + }, + "scripts": { + "clean": "rimraf ./build/*", + "build:types": "tsc --emitDeclarationOnly --outDir ./build/types", + "build:package": "d2-app-scripts build", + "build": "concurrently -n build,types \"yarn build:package\" \"yarn build:types\"", + "watch": "NODE_ENV=development concurrently -n build,types \"yarn build:package --watch\" \"yarn build:types --watch\"", + "type-check": "tsc --noEmit --allowJs --checkJs", + "type-check:watch": "yarn type-check --watch", + "test": "d2-app-scripts test", + "coverage": "yarn test --coverage" + } +} diff --git a/services/plugin/src/PluginError.tsx b/services/plugin/src/PluginError.tsx new file mode 100644 index 000000000..186293e3f --- /dev/null +++ b/services/plugin/src/PluginError.tsx @@ -0,0 +1,58 @@ +import { useDataQuery } from '@dhis2/app-service-data' +import React from 'react' + +const meQuery = { + me: { + resource: 'me', + fields: ['authorities'], + }, +} + +const PluginError = ({ + missingEntryPoint, + showDownload, + appShortName, + missingProps, +}: { + missingEntryPoint: boolean + showDownload: boolean + appShortName?: string + missingProps: string[] | null +}) => { + const { data }: { data?: any } = useDataQuery(meQuery) // cast to deal with types for now... + const canAddApp = + data?.me?.authorities?.includes('ALL') || + data?.me?.authorities?.includes('M_dhis-web-app-management') + return ( + <> +

Plugin unavailable

+ {missingEntryPoint ? ( + <> +

A suitable plugin was not found.

+ {showDownload && canAddApp ? ( +

+ e.stopPropagation()} + target="_blank" + rel="noopener noreferrer" + href="/dhis-web-app-management/index.html#/app-hub" + > + {`Install the ${appShortName}} app from the App Hub`} + +

+ ) : null} + + ) : ( + <> + {!missingEntryPoint && + missingProps && + missingProps?.length > 0 && ( +

{`The following required props were not provided: ${missingProps.join()}`}

+ )} + + )} + + ) +} + +export default PluginError diff --git a/services/plugin/src/PluginSender.tsx b/services/plugin/src/PluginSender.tsx new file mode 100644 index 000000000..67c36fe15 --- /dev/null +++ b/services/plugin/src/PluginSender.tsx @@ -0,0 +1,125 @@ +import { useDataQuery } from '@dhis2/app-service-data' +import postRobot from 'post-robot' +import React, { useCallback, useEffect, useRef, useState } from 'react' +import PluginError from './PluginError' + +const appsInfoQuery = { + apps: { + resource: 'apps', + }, +} + +// sample logic subject to change depending on actual endpoint details +const getPluginEntryPoint = ({ + apps, + appShortName, +}: { + apps: any + appShortName?: string +}): string => { + return apps.find( + ({ short_name }: { short_name: string }) => + short_name && short_name === appShortName + )?.pluginLaunchUrl +} + +export const PluginSender = ({ + pluginSource, + pluginShortName, + ...propsToPass +}: { + pluginSource?: string + pluginShortName?: string + propsToPass: any +}): JSX.Element => { + const iframeRef = useRef(null) + + const [missingProps, setMissingProps] = useState(null) + + const { data } = useDataQuery(appsInfoQuery) + const pluginEntryPoint = + pluginSource ?? + getPluginEntryPoint({ + apps: data?.apps || [], + appShortName: pluginShortName, + }) + + const [communicationReceived, setCommunicationReceived] = + useState(false) + + const updateMissingProps = useCallback( + (propsIdentified: string[] | null) => { + if (propsIdentified?.join() !== missingProps?.join()) { + setMissingProps(propsIdentified) + } + }, + [missingProps, setMissingProps] + ) + + useEffect(() => { + console.log('pluginsender props update') + if (iframeRef?.current) { + const iframeProps = { ...propsToPass, updateMissingProps } + + // if iframe has not sent initial request, set up a listener + if (!communicationReceived) { + const listener = postRobot.on( + 'getPropsFromParent', + // listen for messages coming only from the iframe rendered by this component + { window: iframeRef.current.contentWindow }, + (): any => { + setCommunicationReceived(true) + return iframeProps + } + ) + return () => listener.cancel() + } + + // if iframe has sent initial request, send new props + if (communicationReceived && iframeRef.current.contentWindow) { + postRobot.send( + iframeRef.current.contentWindow, + 'updated', + iframeProps + ) + } + } + }, [propsToPass, communicationReceived, updateMissingProps]) + + if (data && !pluginEntryPoint) { + return ( + + ) + } + + return ( + <> + {/* if props are missing, keep iframe hidden so that it can received updated props */} + {missingProps && missingProps?.length > 0 && ( + + )} + {pluginEntryPoint ? ( + + ) : null} + + ) +} diff --git a/services/plugin/src/PluginWrapper.tsx b/services/plugin/src/PluginWrapper.tsx new file mode 100644 index 000000000..1de6ff0ee --- /dev/null +++ b/services/plugin/src/PluginWrapper.tsx @@ -0,0 +1,69 @@ +import postRobot from 'post-robot' +import { useCallback, useEffect, useState } from 'react' + +export const PluginWrapper = ({ + requiredProps, + children, +}: { + requiredProps: [string] + children: any +}): any => { + const [propsFromParent, setPropsFromParent] = useState() + + const receivePropsFromParent = useCallback( + (event: any): void => { + const { data: receivedProps } = event + const { updateMissingProps, ...explictlyPassedProps } = + receivedProps + + setPropsFromParent(explictlyPassedProps) + + // check for required props + const missingProps = requiredProps?.filter( + (prop) => !explictlyPassedProps[prop] + ) + + // if there are missing props, pass those back to parent + if (missingProps && missingProps.length > 0) { + updateMissingProps(missingProps.sort()) + console.error( + `The following required props were not provided: ${missingProps.join( + ',' + )}` + ) + } else { + updateMissingProps(null) + } + }, + [requiredProps] + ) + + useEffect(() => { + // make first request for props to communicate that iframe is ready + postRobot + .send(window.top, 'getPropsFromParent') + .then(receivePropsFromParent) + .catch((err: Error) => { + console.error(err) + }) + }, [receivePropsFromParent]) + + useEffect(() => { + // set up listener to listen for subsequent sends from parent window + const listener = postRobot.on( + 'updated', + { window: window.top }, + (event): any => { + receivePropsFromParent(event) + } + ) + + return () => listener.cancel() + }, [receivePropsFromParent]) + + useEffect(() => { + console.log('pluginwrapper rerender') + }) + + return children({ ...propsFromParent }) +} diff --git a/services/plugin/src/index.ts b/services/plugin/src/index.ts new file mode 100644 index 000000000..846e52059 --- /dev/null +++ b/services/plugin/src/index.ts @@ -0,0 +1,3 @@ +export { PluginSender } from './PluginSender' + +export { PluginWrapper } from './PluginWrapper' diff --git a/services/plugin/src/setupRTL.ts b/services/plugin/src/setupRTL.ts new file mode 100644 index 000000000..0deaa77ec --- /dev/null +++ b/services/plugin/src/setupRTL.ts @@ -0,0 +1,5 @@ +import '@testing-library/jest-dom/extend-expect' + +process.on('unhandledRejection', (err) => { + throw err +}) diff --git a/services/plugin/src/types.ts b/services/plugin/src/types.ts new file mode 100644 index 000000000..715161bde --- /dev/null +++ b/services/plugin/src/types.ts @@ -0,0 +1 @@ +import { ReactNode } from 'react' diff --git a/services/plugin/tsconfig.json b/services/plugin/tsconfig.json new file mode 100644 index 000000000..3e229a39d --- /dev/null +++ b/services/plugin/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"], + "exclude": [ + "src/setupRTL.ts", + "src/__tests__", + "**/*.test.ts", + "**/*.test.tsx" + ] +} From b14952b137971f625283c8de60afac061176b80e Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Wed, 25 Jan 2023 16:11:14 +0100 Subject: [PATCH 03/10] fix: clean up, add useless test --- services/plugin/src/PluginSender.tsx | 1 - services/plugin/src/PluginWrapper.tsx | 4 ---- .../plugin/src/__tests__/integration.test.tsx | 20 +++++++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 services/plugin/src/__tests__/integration.test.tsx diff --git a/services/plugin/src/PluginSender.tsx b/services/plugin/src/PluginSender.tsx index 67c36fe15..e0e30a818 100644 --- a/services/plugin/src/PluginSender.tsx +++ b/services/plugin/src/PluginSender.tsx @@ -57,7 +57,6 @@ export const PluginSender = ({ ) useEffect(() => { - console.log('pluginsender props update') if (iframeRef?.current) { const iframeProps = { ...propsToPass, updateMissingProps } diff --git a/services/plugin/src/PluginWrapper.tsx b/services/plugin/src/PluginWrapper.tsx index 1de6ff0ee..638670195 100644 --- a/services/plugin/src/PluginWrapper.tsx +++ b/services/plugin/src/PluginWrapper.tsx @@ -61,9 +61,5 @@ export const PluginWrapper = ({ return () => listener.cancel() }, [receivePropsFromParent]) - useEffect(() => { - console.log('pluginwrapper rerender') - }) - return children({ ...propsFromParent }) } diff --git a/services/plugin/src/__tests__/integration.test.tsx b/services/plugin/src/__tests__/integration.test.tsx new file mode 100644 index 000000000..f0f67e223 --- /dev/null +++ b/services/plugin/src/__tests__/integration.test.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import PluginError from '../PluginError' + +// empty tests (to no trigger test failure) +describe('', () => { + it('should render without failing', async () => { + const missingEntryPoint = false + const showDownload = false + const appShortName = 'some_app' + const missingProps = null + const wrapper = () => ( + + ) + }) +}) From 46c9219692b130ffde513b35e1e3e3cc278ae7f2 Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Tue, 31 Jan 2023 17:01:06 +0100 Subject: [PATCH 04/10] chore: start pluginwrapper error boundary --- services/plugin/src/PluginErrorBoundary.tsx | 31 +++++++++++++++++++++ services/plugin/src/PluginWrapper.tsx | 9 ++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 services/plugin/src/PluginErrorBoundary.tsx diff --git a/services/plugin/src/PluginErrorBoundary.tsx b/services/plugin/src/PluginErrorBoundary.tsx new file mode 100644 index 000000000..9775f764c --- /dev/null +++ b/services/plugin/src/PluginErrorBoundary.tsx @@ -0,0 +1,31 @@ +import * as React from 'react' + +type ErrorBoundaryState = { error: Error | null } + +export class PluginErrorBoundary extends React.Component< + {}, + ErrorBoundaryState +> { + constructor(props: any) { + super(props) + this.state = { + error: null, + } + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.log('special handling for within plugin errors would be here') + console.error(error) + this.setState({ + error, + }) + } + + render() { + if (this.state.error) { + return

this is the pluginwrapper error boundary

+ } + + return this.props.children + } +} diff --git a/services/plugin/src/PluginWrapper.tsx b/services/plugin/src/PluginWrapper.tsx index 638670195..91c4e95ba 100644 --- a/services/plugin/src/PluginWrapper.tsx +++ b/services/plugin/src/PluginWrapper.tsx @@ -1,5 +1,6 @@ import postRobot from 'post-robot' -import { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' +import { PluginErrorBoundary } from './PluginErrorBoundary' export const PluginWrapper = ({ requiredProps, @@ -61,5 +62,9 @@ export const PluginWrapper = ({ return () => listener.cancel() }, [receivePropsFromParent]) - return children({ ...propsFromParent }) + return ( + + {children({ ...propsFromParent })} + + ) } From ddc425186657b7457022f45614c6e8ef5c5a421f Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Wed, 1 Feb 2023 20:14:49 +0100 Subject: [PATCH 05/10] chore: sample error --- services/plugin/src/PluginErrorBoundary.tsx | 6 +++++- services/plugin/src/PluginWrapper.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/services/plugin/src/PluginErrorBoundary.tsx b/services/plugin/src/PluginErrorBoundary.tsx index 9775f764c..489f154ea 100644 --- a/services/plugin/src/PluginErrorBoundary.tsx +++ b/services/plugin/src/PluginErrorBoundary.tsx @@ -1,9 +1,10 @@ import * as React from 'react' +type ErrorBoundaryProps = { children: any; onCustomError: Function | null } type ErrorBoundaryState = { error: Error | null } export class PluginErrorBoundary extends React.Component< - {}, + ErrorBoundaryProps, ErrorBoundaryState > { constructor(props: any) { @@ -15,6 +16,9 @@ export class PluginErrorBoundary extends React.Component< componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.log('special handling for within plugin errors would be here') + if (this.props.onCustomError) { + this.props.onCustomError(error) + } console.error(error) this.setState({ error, diff --git a/services/plugin/src/PluginWrapper.tsx b/services/plugin/src/PluginWrapper.tsx index 91c4e95ba..48cdb69d4 100644 --- a/services/plugin/src/PluginWrapper.tsx +++ b/services/plugin/src/PluginWrapper.tsx @@ -63,7 +63,7 @@ export const PluginWrapper = ({ }, [receivePropsFromParent]) return ( - + {children({ ...propsFromParent })} ) From c72fc6eac576ce043ab706e13030497a0dab3d3a Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Mon, 6 Feb 2023 15:28:06 +0100 Subject: [PATCH 06/10] fix: custom error handling --- runtime/src/index.ts | 7 ++++- services/plugin/src/PluginContext.tsx | 28 +++++++++++++++++++ services/plugin/src/PluginSender.tsx | 15 +++++++---- services/plugin/src/PluginWrapper.tsx | 39 +++++++++++++++++---------- services/plugin/src/index.ts | 2 ++ 5 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 services/plugin/src/PluginContext.tsx diff --git a/runtime/src/index.ts b/runtime/src/index.ts index 3339b4d45..e0e9dd5d7 100644 --- a/runtime/src/index.ts +++ b/runtime/src/index.ts @@ -22,6 +22,11 @@ export { clearSensitiveCaches, } from '@dhis2/app-service-offline' -export { PluginSender, PluginWrapper } from '@dhis2/app-service-plugin' +export { + PluginSender, + PluginWrapper, + usePluginErrorContext, + PluginErrorProvider, +} from '@dhis2/app-service-plugin' export { Provider } from './Provider' diff --git a/services/plugin/src/PluginContext.tsx b/services/plugin/src/PluginContext.tsx new file mode 100644 index 000000000..cefc26c64 --- /dev/null +++ b/services/plugin/src/PluginContext.tsx @@ -0,0 +1,28 @@ +import React, { createContext, Dispatch, useContext, useState } from 'react' + +type PluginErrorContextType = { + onPluginError: any + setOnPluginError: any +} + +const PluginErrorContext = createContext({ + onPluginError: null, + setOnPluginError: null, +}) + +const PluginErrorProvider = ({ children }: { children: React.Component }) => { + const [onPluginError, setOnPluginError] = useState(null) + const providerValue = { + onPluginError, + setOnPluginError, + } + return ( + + {children} + + ) +} + +const usePluginErrorContext = () => useContext(PluginErrorContext) + +export { PluginErrorContext, PluginErrorProvider, usePluginErrorContext } diff --git a/services/plugin/src/PluginSender.tsx b/services/plugin/src/PluginSender.tsx index e0e30a818..4c00429b0 100644 --- a/services/plugin/src/PluginSender.tsx +++ b/services/plugin/src/PluginSender.tsx @@ -76,11 +76,16 @@ export const PluginSender = ({ // if iframe has sent initial request, send new props if (communicationReceived && iframeRef.current.contentWindow) { - postRobot.send( - iframeRef.current.contentWindow, - 'updated', - iframeProps - ) + postRobot + .send( + iframeRef.current.contentWindow, + 'updated', + iframeProps + ) + .catch((err) => { + // log postRobot errors, but do not bubble them up + console.error(err) + }) } } }, [propsToPass, communicationReceived, updateMissingProps]) diff --git a/services/plugin/src/PluginWrapper.tsx b/services/plugin/src/PluginWrapper.tsx index 48cdb69d4..7de2e087e 100644 --- a/services/plugin/src/PluginWrapper.tsx +++ b/services/plugin/src/PluginWrapper.tsx @@ -1,6 +1,7 @@ import postRobot from 'post-robot' import React, { useCallback, useEffect, useState } from 'react' import { PluginErrorBoundary } from './PluginErrorBoundary' +import { usePluginErrorContext } from './PluginContext' export const PluginWrapper = ({ requiredProps, @@ -9,6 +10,7 @@ export const PluginWrapper = ({ requiredProps: [string] children: any }): any => { + const { setOnPluginError } = usePluginErrorContext() const [propsFromParent, setPropsFromParent] = useState() const receivePropsFromParent = useCallback( @@ -35,19 +37,27 @@ export const PluginWrapper = ({ } else { updateMissingProps(null) } + + if (explictlyPassedProps.onError && setOnPluginError) { + setOnPluginError(() => (error: Error) => { + explictlyPassedProps.onError(error) + }) + } }, - [requiredProps] + [requiredProps, setOnPluginError] ) useEffect(() => { - // make first request for props to communicate that iframe is ready - postRobot - .send(window.top, 'getPropsFromParent') - .then(receivePropsFromParent) - .catch((err: Error) => { - console.error(err) - }) - }, [receivePropsFromParent]) + if (setOnPluginError) { + // make first request for props to communicate that iframe is ready + postRobot + .send(window.top, 'getPropsFromParent') + .then(receivePropsFromParent) + .catch((err: Error) => { + console.error(err) + }) + } + }, [receivePropsFromParent, setOnPluginError]) useEffect(() => { // set up listener to listen for subsequent sends from parent window @@ -62,9 +72,10 @@ export const PluginWrapper = ({ return () => listener.cancel() }, [receivePropsFromParent]) - return ( - - {children({ ...propsFromParent })} - - ) + return children({ ...propsFromParent }) + // return ( + // + // {children({ ...propsFromParent })} + // + // ) } diff --git a/services/plugin/src/index.ts b/services/plugin/src/index.ts index 846e52059..2136a0033 100644 --- a/services/plugin/src/index.ts +++ b/services/plugin/src/index.ts @@ -1,3 +1,5 @@ export { PluginSender } from './PluginSender' export { PluginWrapper } from './PluginWrapper' + +export * from './PluginContext' From bda6a4352fe0ad8a076f55040e3fe702f9d0c4eb Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Thu, 9 Feb 2023 08:44:43 +0100 Subject: [PATCH 07/10] feat: plugin wrappers, errors + alerts --- runtime/src/Provider.tsx | 41 +++++++-- runtime/src/index.ts | 4 +- services/alerts/src/AlertsManagerContext.ts | 3 + services/alerts/src/AlertsProvider.tsx | 12 ++- services/alerts/src/index.ts | 1 + services/alerts/src/makeAlertsManager.ts | 4 +- services/alerts/src/types.ts | 3 + services/alerts/src/useAlert.ts | 29 +++++-- services/plugin/package.json | 3 +- services/plugin/src/PluginContext.tsx | 43 ++++++++-- services/plugin/src/PluginError.tsx | 14 +-- services/plugin/src/PluginErrorBoundary.tsx | 35 -------- services/plugin/src/PluginSender.tsx | 72 +++++++--------- services/plugin/src/PluginWrapper.tsx | 94 +++++++++++++++------ services/plugin/src/types.ts | 2 + 15 files changed, 218 insertions(+), 142 deletions(-) delete mode 100644 services/plugin/src/PluginErrorBoundary.tsx diff --git a/runtime/src/Provider.tsx b/runtime/src/Provider.tsx index 19ad016d8..10e0d1453 100644 --- a/runtime/src/Provider.tsx +++ b/runtime/src/Provider.tsx @@ -3,26 +3,53 @@ import { ConfigProvider } from '@dhis2/app-service-config' import { Config } from '@dhis2/app-service-config/build/types/types' import { DataProvider } from '@dhis2/app-service-data' import { OfflineProvider } from '@dhis2/app-service-offline' +import { PluginProvider, usePluginContext } from '@dhis2/app-service-plugin' import React from 'react' +// passed this way to avoid setting plugin service as peer dependency in alerts +const DemoPass = ({ + plugin, + offlineInterface, + children, +}: { + plugin: boolean + offlineInterface?: any + children: React.ReactNode +}) => { + const { parentAlertsAdd, showAlertsInPlugin } = usePluginContext() + return ( + + + + {children} + + + + ) +} + type ProviderInput = { config: Config children: React.ReactNode offlineInterface?: any // temporary until offline service has types + plugin: boolean } export const Provider = ({ config, children, offlineInterface, + plugin, }: ProviderInput) => ( - - - - {children} - - - + + + {children} + + ) diff --git a/runtime/src/index.ts b/runtime/src/index.ts index e0e9dd5d7..748267127 100644 --- a/runtime/src/index.ts +++ b/runtime/src/index.ts @@ -25,8 +25,8 @@ export { export { PluginSender, PluginWrapper, - usePluginErrorContext, - PluginErrorProvider, + usePluginContext, + PluginProvider, } from '@dhis2/app-service-plugin' export { Provider } from './Provider' diff --git a/services/alerts/src/AlertsManagerContext.ts b/services/alerts/src/AlertsManagerContext.ts index 0c3ed4d8b..9c3527fa9 100644 --- a/services/alerts/src/AlertsManagerContext.ts +++ b/services/alerts/src/AlertsManagerContext.ts @@ -9,6 +9,9 @@ const placeholder = () => { const defaultAlertsManager: AlertsManager = { add: placeholder, + plugin: false, + parentAlertsAdd: null, + showAlertsInPlugin: false, } export const AlertsManagerContext = diff --git a/services/alerts/src/AlertsProvider.tsx b/services/alerts/src/AlertsProvider.tsx index e59cbf766..fc8876ce4 100644 --- a/services/alerts/src/AlertsProvider.tsx +++ b/services/alerts/src/AlertsProvider.tsx @@ -5,17 +5,25 @@ import { makeAlertsManager } from './makeAlertsManager' import type { Alert, AlertsManager } from './types' export const AlertsProvider = ({ + plugin, + parentAlertsAdd, + showAlertsInPlugin, children, }: { + plugin: boolean + parentAlertsAdd: any + showAlertsInPlugin: boolean children: React.ReactNode }): ReactElement => { const [alerts, setAlerts] = useState([]) const [alertsManager] = useState(() => - makeAlertsManager(setAlerts) + makeAlertsManager(setAlerts, plugin) ) return ( - + {children} diff --git a/services/alerts/src/index.ts b/services/alerts/src/index.ts index f2e0367bd..f02489ab6 100644 --- a/services/alerts/src/index.ts +++ b/services/alerts/src/index.ts @@ -1,3 +1,4 @@ export { AlertsProvider } from './AlertsProvider' export { useAlerts } from './useAlerts' export { useAlert } from './useAlert' +export { AlertsManagerContext } from './AlertsManagerContext' diff --git a/services/alerts/src/makeAlertsManager.ts b/services/alerts/src/makeAlertsManager.ts index b97bf264e..6963d58db 100644 --- a/services/alerts/src/makeAlertsManager.ts +++ b/services/alerts/src/makeAlertsManager.ts @@ -4,7 +4,8 @@ const toVisibleAlertsArray = (alertsMap: AlertsMap) => Array.from(alertsMap.values()) export const makeAlertsManager = ( - setAlerts: React.Dispatch> + setAlerts: React.Dispatch>, + plugin: boolean ): AlertsManager => { const alertsMap: AlertsMap = new Map() let id = 0 @@ -29,5 +30,6 @@ export const makeAlertsManager = ( return { add, + plugin, } } diff --git a/services/alerts/src/types.ts b/services/alerts/src/types.ts index 73c563852..654a0560b 100644 --- a/services/alerts/src/types.ts +++ b/services/alerts/src/types.ts @@ -15,4 +15,7 @@ export type AlertsMap = Map export type AlertsManager = { add: (alert: Alert, alertRef: AlertRef) => Alert + plugin: boolean + parentAlertsAdd?: any + showAlertsInPlugin?: boolean } diff --git a/services/alerts/src/useAlert.ts b/services/alerts/src/useAlert.ts index 87454cace..2faba53fd 100644 --- a/services/alerts/src/useAlert.ts +++ b/services/alerts/src/useAlert.ts @@ -6,7 +6,8 @@ export const useAlert = ( message: string | ((props: any) => string), options: AlertOptions | ((props: any) => AlertOptions) = {} ): { show: (props?: any) => void; hide: () => void } => { - const { add }: AlertsManager = useContext(AlertsManagerContext) + const { add, plugin, parentAlertsAdd, showAlertsInPlugin }: AlertsManager = + useContext(AlertsManagerContext) const alertRef = useRef(null) const show = useCallback( @@ -17,15 +18,25 @@ export const useAlert = ( const resolvedOptions = typeof options === 'function' ? options(props) : options - alertRef.current = add( - { - message: resolvedMessage, - options: resolvedOptions, - }, - alertRef - ) + if (plugin && parentAlertsAdd && !showAlertsInPlugin) { + alertRef.current = parentAlertsAdd( + { + message: resolvedMessage, + options: resolvedOptions, + }, + alertRef + ) + } else { + alertRef.current = add( + { + message: resolvedMessage, + options: resolvedOptions, + }, + alertRef + ) + } }, - [add, message, options] + [add, parentAlertsAdd, message, options] ) const hide = useCallback(() => { diff --git a/services/plugin/package.json b/services/plugin/package.json index ec3830cff..cb8e64325 100644 --- a/services/plugin/package.json +++ b/services/plugin/package.json @@ -25,7 +25,8 @@ "prop-types": "^15.7.2", "react": "^16.8.6", "react-dom": "^16.8.6", - "@dhis2/app-service-data": "3.7.0" + "@dhis2/app-service-alerts": "3.7.0", + "@dhis2/app-service-data": "3.7.0" }, "scripts": { "clean": "rimraf ./build/*", diff --git a/services/plugin/src/PluginContext.tsx b/services/plugin/src/PluginContext.tsx index cefc26c64..c66352307 100644 --- a/services/plugin/src/PluginContext.tsx +++ b/services/plugin/src/PluginContext.tsx @@ -1,28 +1,55 @@ -import React, { createContext, Dispatch, useContext, useState } from 'react' +import React, { ReactElement, createContext, useContext, useState } from 'react' -type PluginErrorContextType = { +type PluginContextType = { onPluginError: any setOnPluginError: any + parentAlertsAdd: any + setParentAlertsAdd: any + showAlertsInPlugin: boolean + setShowAlertsInPlugin: any + clearPluginError: any + setClearPluginError: any } -const PluginErrorContext = createContext({ +const PluginContext = createContext({ onPluginError: null, setOnPluginError: null, + parentAlertsAdd: null, + setParentAlertsAdd: null, + showAlertsInPlugin: false, + setShowAlertsInPlugin: null, + clearPluginError: null, + setClearPluginError: null, }) -const PluginErrorProvider = ({ children }: { children: React.Component }) => { +// TO DO: implement different component if not plugin + +const PluginProvider = ({ + children, +}: { + children: React.ReactNode +}): ReactElement => { const [onPluginError, setOnPluginError] = useState(null) + const [parentAlertsAdd, setParentAlertsAdd] = useState(null) + const [showAlertsInPlugin, setShowAlertsInPlugin] = useState(false) + const [clearPluginError, setClearPluginError] = useState(null) const providerValue = { onPluginError, setOnPluginError, + parentAlertsAdd, + setParentAlertsAdd, + showAlertsInPlugin, + setShowAlertsInPlugin, + clearPluginError, + setClearPluginError, } return ( - + {children} - + ) } -const usePluginErrorContext = () => useContext(PluginErrorContext) +const usePluginContext = () => useContext(PluginContext) -export { PluginErrorContext, PluginErrorProvider, usePluginErrorContext } +export { PluginContext, PluginProvider, usePluginContext } diff --git a/services/plugin/src/PluginError.tsx b/services/plugin/src/PluginError.tsx index 186293e3f..145fb5f1a 100644 --- a/services/plugin/src/PluginError.tsx +++ b/services/plugin/src/PluginError.tsx @@ -8,16 +8,16 @@ const meQuery = { }, } +// plugin error component (e.g. for dealing with missing/inaccessible plugin) + const PluginError = ({ missingEntryPoint, showDownload, appShortName, - missingProps, }: { missingEntryPoint: boolean showDownload: boolean appShortName?: string - missingProps: string[] | null }) => { const { data }: { data?: any } = useDataQuery(meQuery) // cast to deal with types for now... const canAddApp = @@ -42,15 +42,7 @@ const PluginError = ({

) : null} - ) : ( - <> - {!missingEntryPoint && - missingProps && - missingProps?.length > 0 && ( -

{`The following required props were not provided: ${missingProps.join()}`}

- )} - - )} + ) : null} ) } diff --git a/services/plugin/src/PluginErrorBoundary.tsx b/services/plugin/src/PluginErrorBoundary.tsx deleted file mode 100644 index 489f154ea..000000000 --- a/services/plugin/src/PluginErrorBoundary.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from 'react' - -type ErrorBoundaryProps = { children: any; onCustomError: Function | null } -type ErrorBoundaryState = { error: Error | null } - -export class PluginErrorBoundary extends React.Component< - ErrorBoundaryProps, - ErrorBoundaryState -> { - constructor(props: any) { - super(props) - this.state = { - error: null, - } - } - - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - console.log('special handling for within plugin errors would be here') - if (this.props.onCustomError) { - this.props.onCustomError(error) - } - console.error(error) - this.setState({ - error, - }) - } - - render() { - if (this.state.error) { - return

this is the pluginwrapper error boundary

- } - - return this.props.children - } -} diff --git a/services/plugin/src/PluginSender.tsx b/services/plugin/src/PluginSender.tsx index 4c00429b0..66d8cf5ed 100644 --- a/services/plugin/src/PluginSender.tsx +++ b/services/plugin/src/PluginSender.tsx @@ -1,6 +1,7 @@ +import { AlertsManagerContext } from '@dhis2/app-service-alerts' import { useDataQuery } from '@dhis2/app-service-data' import postRobot from 'post-robot' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import React, { useContext, useEffect, useRef, useState } from 'react' import PluginError from './PluginError' const appsInfoQuery = { @@ -34,7 +35,7 @@ export const PluginSender = ({ }): JSX.Element => { const iframeRef = useRef(null) - const [missingProps, setMissingProps] = useState(null) + const { add: alertsAdd } = useContext(AlertsManagerContext) const { data } = useDataQuery(appsInfoQuery) const pluginEntryPoint = @@ -47,21 +48,19 @@ export const PluginSender = ({ const [communicationReceived, setCommunicationReceived] = useState(false) - const updateMissingProps = useCallback( - (propsIdentified: string[] | null) => { - if (propsIdentified?.join() !== missingProps?.join()) { - setMissingProps(propsIdentified) - } - }, - [missingProps, setMissingProps] - ) + const [inErrorState, setInErrorState] = useState(false) useEffect(() => { if (iframeRef?.current) { - const iframeProps = { ...propsToPass, updateMissingProps } + const iframeProps = { + ...propsToPass, + alertsAdd, + setInErrorState, + setCommunicationReceived, + } // if iframe has not sent initial request, set up a listener - if (!communicationReceived) { + if (!communicationReceived && !inErrorState) { const listener = postRobot.on( 'getPropsFromParent', // listen for messages coming only from the iframe rendered by this component @@ -75,7 +74,11 @@ export const PluginSender = ({ } // if iframe has sent initial request, send new props - if (communicationReceived && iframeRef.current.contentWindow) { + if ( + communicationReceived && + iframeRef.current.contentWindow && + !inErrorState + ) { postRobot .send( iframeRef.current.contentWindow, @@ -88,7 +91,7 @@ export const PluginSender = ({ }) } } - }, [propsToPass, communicationReceived, updateMissingProps]) + }, [propsToPass, communicationReceived, inErrorState, alertsAdd]) if (data && !pluginEntryPoint) { return ( @@ -96,34 +99,23 @@ export const PluginSender = ({ missingEntryPoint={true} showDownload={false} appShortName={pluginShortName} - missingProps={missingProps} /> ) } - return ( - <> - {/* if props are missing, keep iframe hidden so that it can received updated props */} - {missingProps && missingProps?.length > 0 && ( - - )} - {pluginEntryPoint ? ( - - ) : null} - - ) + if (pluginEntryPoint) { + return ( + + ) + } + + return <> } diff --git a/services/plugin/src/PluginWrapper.tsx b/services/plugin/src/PluginWrapper.tsx index 7de2e087e..518e84093 100644 --- a/services/plugin/src/PluginWrapper.tsx +++ b/services/plugin/src/PluginWrapper.tsx @@ -1,7 +1,6 @@ import postRobot from 'post-robot' -import React, { useCallback, useEffect, useState } from 'react' -import { PluginErrorBoundary } from './PluginErrorBoundary' -import { usePluginErrorContext } from './PluginContext' +import { useCallback, useEffect, useState } from 'react' +import { usePluginContext } from './PluginContext' export const PluginWrapper = ({ requiredProps, @@ -10,41 +9,80 @@ export const PluginWrapper = ({ requiredProps: [string] children: any }): any => { - const { setOnPluginError } = usePluginErrorContext() + const { + setOnPluginError, + setClearPluginError, + setParentAlertsAdd, + setShowAlertsInPlugin, + } = usePluginContext() const [propsFromParent, setPropsFromParent] = useState() + const [propsThatAreMissing, setPropsThatAreMissing] = useState< + Array + >([]) const receivePropsFromParent = useCallback( (event: any): void => { const { data: receivedProps } = event - const { updateMissingProps, ...explictlyPassedProps } = - receivedProps + const { + setInErrorState, + setCommunicationReceived, + alertsAdd, + showAlertsInPlugin, + onError, + ...explicitlyPassedProps + } = receivedProps - setPropsFromParent(explictlyPassedProps) + setPropsFromParent(explicitlyPassedProps) // check for required props const missingProps = requiredProps?.filter( - (prop) => !explictlyPassedProps[prop] + (prop) => !explicitlyPassedProps[prop] ) - // if there are missing props, pass those back to parent + // if there are missing props, set to state to throw error if (missingProps && missingProps.length > 0) { - updateMissingProps(missingProps.sort()) - console.error( - `The following required props were not provided: ${missingProps.join( - ',' - )}` - ) - } else { - updateMissingProps(null) + console.error(`These props are missing: ${missingProps.join()}`) + setPropsThatAreMissing(missingProps) } - if (explictlyPassedProps.onError && setOnPluginError) { - setOnPluginError(() => (error: Error) => { - explictlyPassedProps.onError(error) + if (setOnPluginError && setInErrorState) { + if (onError) { + setOnPluginError(() => (error: Error) => { + setCommunicationReceived(false) + setInErrorState(true) + onError(error) + }) + } else { + setOnPluginError(() => () => { + setCommunicationReceived(false) + setInErrorState(true) + }) + } + } + + if (setClearPluginError && setInErrorState) { + setClearPluginError(() => () => { + setInErrorState(false) + }) + } + + if (setParentAlertsAdd && alertsAdd) { + setParentAlertsAdd(() => (alert: any, alertRef: any) => { + alertsAdd(alert, alertRef) }) } + + if (showAlertsInPlugin && setShowAlertsInPlugin) { + setShowAlertsInPlugin(Boolean(showAlertsInPlugin)) + } }, - [requiredProps, setOnPluginError] + [ + requiredProps, + setOnPluginError, + setClearPluginError, + setParentAlertsAdd, + setShowAlertsInPlugin, + ] ) useEffect(() => { @@ -72,10 +110,14 @@ export const PluginWrapper = ({ return () => listener.cancel() }, [receivePropsFromParent]) + // throw error if props are missing + useEffect(() => { + if (propsThatAreMissing.length > 0) { + throw new Error( + `These props are missing: ${propsThatAreMissing.join()}` + ) + } + }, [propsThatAreMissing]) + return children({ ...propsFromParent }) - // return ( - // - // {children({ ...propsFromParent })} - // - // ) } diff --git a/services/plugin/src/types.ts b/services/plugin/src/types.ts index 715161bde..f00b281de 100644 --- a/services/plugin/src/types.ts +++ b/services/plugin/src/types.ts @@ -1 +1,3 @@ import { ReactNode } from 'react' + +// file is a placeholder to allow build From 1681f6ec557d64771d9c352b6e819afd0e6be763 Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Fri, 17 Feb 2023 16:14:25 +0100 Subject: [PATCH 08/10] chore: move plugin wrapper logic to app-platform --- examples/cra/yarn.lock | 45 ------- examples/query-playground/yarn.lock | 45 ------- runtime/package.json | 6 +- runtime/src/Provider.tsx | 47 +++---- runtime/src/index.ts | 7 +- services/alerts/src/useAlert.ts | 2 +- services/plugin/package.json | 6 +- services/plugin/src/PluginContext.tsx | 55 -------- services/plugin/src/PluginError.tsx | 51 ++------ services/plugin/src/PluginSender.tsx | 1 - services/plugin/src/PluginWrapper.tsx | 123 ------------------ .../plugin/src/__tests__/integration.test.tsx | 4 - services/plugin/src/index.ts | 4 - 13 files changed, 35 insertions(+), 361 deletions(-) delete mode 100644 services/plugin/src/PluginContext.tsx delete mode 100644 services/plugin/src/PluginWrapper.tsx diff --git a/examples/cra/yarn.lock b/examples/cra/yarn.lock index ae729ea73..08d7548ad 100644 --- a/examples/cra/yarn.lock +++ b/examples/cra/yarn.lock @@ -1062,7 +1062,6 @@ "@dhis2/app-service-offline" "3.7.0" "@dhis2/app-service-plugin" "3.7.0" "@dhis2/d2-i18n" "^1.1.0" - post-robot "^10.0.46" "@dhis2/app-service-alerts@3.7.0", "@dhis2/app-service-alerts@file:../../services/alerts": version "3.7.0" @@ -2273,15 +2272,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -belter@^1.0.41: - version "1.0.190" - resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" - integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== - dependencies: - cross-domain-safe-weakmap "^1" - cross-domain-utils "^2" - zalgo-promise "^1" - big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -3166,20 +3156,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: - version "1.0.29" - resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" - integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== - dependencies: - cross-domain-utils "^2.0.0" - -cross-domain-utils@^2, cross-domain-utils@^2.0.0: - version "2.0.38" - resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" - integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== - dependencies: - zalgo-promise "^1.0.11" - cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -7765,17 +7741,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -post-robot@^10.0.46: - version "10.0.46" - resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" - integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== - dependencies: - belter "^1.0.41" - cross-domain-safe-weakmap "^1.0.1" - cross-domain-utils "^2.0.0" - universal-serialize "^1.0.4" - zalgo-promise "^1.0.3" - postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -10384,11 +10349,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -universal-serialize@^1.0.4: - version "1.0.10" - resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" - integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -11106,8 +11066,3 @@ yargs@^15.3.1: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^18.1.1" - -zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: - version "1.0.48" - resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" - integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== diff --git a/examples/query-playground/yarn.lock b/examples/query-playground/yarn.lock index 38258da7f..232e71aad 100644 --- a/examples/query-playground/yarn.lock +++ b/examples/query-playground/yarn.lock @@ -1808,7 +1808,6 @@ "@dhis2/app-service-offline" "3.7.0" "@dhis2/app-service-plugin" "3.7.0" "@dhis2/d2-i18n" "^1.1.0" - post-robot "^10.0.46" "@dhis2/app-service-alerts@3.7.0", "@dhis2/app-service-alerts@3.8.0", "@dhis2/app-service-alerts@file:../../services/alerts": version "3.7.0" @@ -3383,15 +3382,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -belter@^1.0.41: - version "1.0.190" - resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" - integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== - dependencies: - cross-domain-safe-weakmap "^1" - cross-domain-utils "^2" - zalgo-promise "^1" - big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -4455,20 +4445,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: - version "1.0.29" - resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" - integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== - dependencies: - cross-domain-utils "^2.0.0" - -cross-domain-utils@^2, cross-domain-utils@^2.0.0: - version "2.0.38" - resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" - integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== - dependencies: - zalgo-promise "^1.0.11" - cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -9658,17 +9634,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -post-robot@^10.0.46: - version "10.0.46" - resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" - integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== - dependencies: - belter "^1.0.41" - cross-domain-safe-weakmap "^1.0.1" - cross-domain-utils "^2.0.0" - universal-serialize "^1.0.4" - zalgo-promise "^1.0.3" - postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -12723,11 +12688,6 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -universal-serialize@^1.0.4: - version "1.0.10" - resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" - integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -13562,11 +13522,6 @@ yargs@^15.0.0, yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.1" -zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: - version "1.0.48" - resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" - integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== - zip-stream@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" diff --git a/runtime/package.json b/runtime/package.json index d5f718ce4..99c6c3aba 100644 --- a/runtime/package.json +++ b/runtime/package.json @@ -28,8 +28,7 @@ "@dhis2/app-service-data": "3.7.0", "@dhis2/app-service-offline": "3.7.0", "@dhis2/app-service-plugin": "3.7.0", - "@dhis2/d2-i18n": "^1.1.0", - "post-robot": "^10.0.46" + "@dhis2/d2-i18n": "^1.1.0" }, "peerDependencies": { "prop-types": "^15.7.2", @@ -41,8 +40,5 @@ "build:package": "d2-app-scripts build", "build": "concurrently -n build,types \"yarn build:package\" \"yarn build:types\"", "test": "echo \"No tests yet!\"" - }, - "devDependencies": { - "@types/post-robot": "^10.0.3" } } diff --git a/runtime/src/Provider.tsx b/runtime/src/Provider.tsx index 10e0d1453..312af148c 100644 --- a/runtime/src/Provider.tsx +++ b/runtime/src/Provider.tsx @@ -3,53 +3,36 @@ import { ConfigProvider } from '@dhis2/app-service-config' import { Config } from '@dhis2/app-service-config/build/types/types' import { DataProvider } from '@dhis2/app-service-data' import { OfflineProvider } from '@dhis2/app-service-offline' -import { PluginProvider, usePluginContext } from '@dhis2/app-service-plugin' import React from 'react' -// passed this way to avoid setting plugin service as peer dependency in alerts -const DemoPass = ({ - plugin, - offlineInterface, - children, -}: { - plugin: boolean - offlineInterface?: any - children: React.ReactNode -}) => { - const { parentAlertsAdd, showAlertsInPlugin } = usePluginContext() - return ( - - - - {children} - - - - ) -} - type ProviderInput = { config: Config children: React.ReactNode offlineInterface?: any // temporary until offline service has types plugin: boolean + parentAlertsAdd: any + showAlertsInPlugin: boolean } export const Provider = ({ config, children, offlineInterface, plugin, + parentAlertsAdd, + showAlertsInPlugin, }: ProviderInput) => ( - - - {children} - - + + + + {children} + + + ) diff --git a/runtime/src/index.ts b/runtime/src/index.ts index 748267127..37607eb5d 100644 --- a/runtime/src/index.ts +++ b/runtime/src/index.ts @@ -22,11 +22,6 @@ export { clearSensitiveCaches, } from '@dhis2/app-service-offline' -export { - PluginSender, - PluginWrapper, - usePluginContext, - PluginProvider, -} from '@dhis2/app-service-plugin' +export { PluginSender } from '@dhis2/app-service-plugin' export { Provider } from './Provider' diff --git a/services/alerts/src/useAlert.ts b/services/alerts/src/useAlert.ts index 2faba53fd..adc84e211 100644 --- a/services/alerts/src/useAlert.ts +++ b/services/alerts/src/useAlert.ts @@ -36,7 +36,7 @@ export const useAlert = ( ) } }, - [add, parentAlertsAdd, message, options] + [add, parentAlertsAdd, message, options, plugin, showAlertsInPlugin] ) const hide = useCallback(() => { diff --git a/services/plugin/package.json b/services/plugin/package.json index cb8e64325..7dc810784 100644 --- a/services/plugin/package.json +++ b/services/plugin/package.json @@ -26,7 +26,11 @@ "react": "^16.8.6", "react-dom": "^16.8.6", "@dhis2/app-service-alerts": "3.7.0", - "@dhis2/app-service-data": "3.7.0" + "@dhis2/app-service-data": "3.7.0", + "post-robot": "^10.0.46" + }, + "devDependencies": { + "@types/post-robot": "^10.0.3" }, "scripts": { "clean": "rimraf ./build/*", diff --git a/services/plugin/src/PluginContext.tsx b/services/plugin/src/PluginContext.tsx deleted file mode 100644 index c66352307..000000000 --- a/services/plugin/src/PluginContext.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { ReactElement, createContext, useContext, useState } from 'react' - -type PluginContextType = { - onPluginError: any - setOnPluginError: any - parentAlertsAdd: any - setParentAlertsAdd: any - showAlertsInPlugin: boolean - setShowAlertsInPlugin: any - clearPluginError: any - setClearPluginError: any -} - -const PluginContext = createContext({ - onPluginError: null, - setOnPluginError: null, - parentAlertsAdd: null, - setParentAlertsAdd: null, - showAlertsInPlugin: false, - setShowAlertsInPlugin: null, - clearPluginError: null, - setClearPluginError: null, -}) - -// TO DO: implement different component if not plugin - -const PluginProvider = ({ - children, -}: { - children: React.ReactNode -}): ReactElement => { - const [onPluginError, setOnPluginError] = useState(null) - const [parentAlertsAdd, setParentAlertsAdd] = useState(null) - const [showAlertsInPlugin, setShowAlertsInPlugin] = useState(false) - const [clearPluginError, setClearPluginError] = useState(null) - const providerValue = { - onPluginError, - setOnPluginError, - parentAlertsAdd, - setParentAlertsAdd, - showAlertsInPlugin, - setShowAlertsInPlugin, - clearPluginError, - setClearPluginError, - } - return ( - - {children} - - ) -} - -const usePluginContext = () => useContext(PluginContext) - -export { PluginContext, PluginProvider, usePluginContext } diff --git a/services/plugin/src/PluginError.tsx b/services/plugin/src/PluginError.tsx index 145fb5f1a..2883f88a5 100644 --- a/services/plugin/src/PluginError.tsx +++ b/services/plugin/src/PluginError.tsx @@ -1,50 +1,23 @@ -import { useDataQuery } from '@dhis2/app-service-data' import React from 'react' -const meQuery = { - me: { - resource: 'me', - fields: ['authorities'], - }, -} - -// plugin error component (e.g. for dealing with missing/inaccessible plugin) +// PLACEHOLDER plugin error component (e.g. for dealing with missing/inaccessible plugin) +// note that d2-i18n does not work with typescript projects, so we cannot currently translate const PluginError = ({ missingEntryPoint, - showDownload, appShortName, }: { missingEntryPoint: boolean - showDownload: boolean appShortName?: string -}) => { - const { data }: { data?: any } = useDataQuery(meQuery) // cast to deal with types for now... - const canAddApp = - data?.me?.authorities?.includes('ALL') || - data?.me?.authorities?.includes('M_dhis-web-app-management') - return ( - <> -

Plugin unavailable

- {missingEntryPoint ? ( - <> -

A suitable plugin was not found.

- {showDownload && canAddApp ? ( -

- e.stopPropagation()} - target="_blank" - rel="noopener noreferrer" - href="/dhis-web-app-management/index.html#/app-hub" - > - {`Install the ${appShortName}} app from the App Hub`} - -

- ) : null} - - ) : null} - - ) -} +}) => ( + <> +

Plugin unavailable

+ {missingEntryPoint ? ( + <> +

{`You do not have access to the requested plugin ${appShortName}, or it is not installed`}

+ + ) : null} + +) export default PluginError diff --git a/services/plugin/src/PluginSender.tsx b/services/plugin/src/PluginSender.tsx index 66d8cf5ed..a72663d2f 100644 --- a/services/plugin/src/PluginSender.tsx +++ b/services/plugin/src/PluginSender.tsx @@ -97,7 +97,6 @@ export const PluginSender = ({ return ( ) diff --git a/services/plugin/src/PluginWrapper.tsx b/services/plugin/src/PluginWrapper.tsx deleted file mode 100644 index 518e84093..000000000 --- a/services/plugin/src/PluginWrapper.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import postRobot from 'post-robot' -import { useCallback, useEffect, useState } from 'react' -import { usePluginContext } from './PluginContext' - -export const PluginWrapper = ({ - requiredProps, - children, -}: { - requiredProps: [string] - children: any -}): any => { - const { - setOnPluginError, - setClearPluginError, - setParentAlertsAdd, - setShowAlertsInPlugin, - } = usePluginContext() - const [propsFromParent, setPropsFromParent] = useState() - const [propsThatAreMissing, setPropsThatAreMissing] = useState< - Array - >([]) - - const receivePropsFromParent = useCallback( - (event: any): void => { - const { data: receivedProps } = event - const { - setInErrorState, - setCommunicationReceived, - alertsAdd, - showAlertsInPlugin, - onError, - ...explicitlyPassedProps - } = receivedProps - - setPropsFromParent(explicitlyPassedProps) - - // check for required props - const missingProps = requiredProps?.filter( - (prop) => !explicitlyPassedProps[prop] - ) - - // if there are missing props, set to state to throw error - if (missingProps && missingProps.length > 0) { - console.error(`These props are missing: ${missingProps.join()}`) - setPropsThatAreMissing(missingProps) - } - - if (setOnPluginError && setInErrorState) { - if (onError) { - setOnPluginError(() => (error: Error) => { - setCommunicationReceived(false) - setInErrorState(true) - onError(error) - }) - } else { - setOnPluginError(() => () => { - setCommunicationReceived(false) - setInErrorState(true) - }) - } - } - - if (setClearPluginError && setInErrorState) { - setClearPluginError(() => () => { - setInErrorState(false) - }) - } - - if (setParentAlertsAdd && alertsAdd) { - setParentAlertsAdd(() => (alert: any, alertRef: any) => { - alertsAdd(alert, alertRef) - }) - } - - if (showAlertsInPlugin && setShowAlertsInPlugin) { - setShowAlertsInPlugin(Boolean(showAlertsInPlugin)) - } - }, - [ - requiredProps, - setOnPluginError, - setClearPluginError, - setParentAlertsAdd, - setShowAlertsInPlugin, - ] - ) - - useEffect(() => { - if (setOnPluginError) { - // make first request for props to communicate that iframe is ready - postRobot - .send(window.top, 'getPropsFromParent') - .then(receivePropsFromParent) - .catch((err: Error) => { - console.error(err) - }) - } - }, [receivePropsFromParent, setOnPluginError]) - - useEffect(() => { - // set up listener to listen for subsequent sends from parent window - const listener = postRobot.on( - 'updated', - { window: window.top }, - (event): any => { - receivePropsFromParent(event) - } - ) - - return () => listener.cancel() - }, [receivePropsFromParent]) - - // throw error if props are missing - useEffect(() => { - if (propsThatAreMissing.length > 0) { - throw new Error( - `These props are missing: ${propsThatAreMissing.join()}` - ) - } - }, [propsThatAreMissing]) - - return children({ ...propsFromParent }) -} diff --git a/services/plugin/src/__tests__/integration.test.tsx b/services/plugin/src/__tests__/integration.test.tsx index f0f67e223..b5270713f 100644 --- a/services/plugin/src/__tests__/integration.test.tsx +++ b/services/plugin/src/__tests__/integration.test.tsx @@ -5,15 +5,11 @@ import PluginError from '../PluginError' describe('', () => { it('should render without failing', async () => { const missingEntryPoint = false - const showDownload = false const appShortName = 'some_app' - const missingProps = null const wrapper = () => ( ) }) diff --git a/services/plugin/src/index.ts b/services/plugin/src/index.ts index 2136a0033..f6913966f 100644 --- a/services/plugin/src/index.ts +++ b/services/plugin/src/index.ts @@ -1,5 +1 @@ export { PluginSender } from './PluginSender' - -export { PluginWrapper } from './PluginWrapper' - -export * from './PluginContext' From c537590176b2e6aebf278653a87705b3417bcc38 Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Fri, 24 Feb 2023 18:57:41 +0100 Subject: [PATCH 09/10] fix: add documentation, clean up --- docs/components/Plugin.md | 63 +++++++++++++++++++ runtime/src/index.ts | 2 +- .../src/{PluginSender.tsx => Plugin.tsx} | 2 +- services/plugin/src/index.ts | 2 +- yarn.lock | 44 ------------- 5 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 docs/components/Plugin.md rename services/plugin/src/{PluginSender.tsx => Plugin.tsx} (99%) diff --git a/docs/components/Plugin.md b/docs/components/Plugin.md new file mode 100644 index 000000000..a4ee4dff7 --- /dev/null +++ b/docs/components/Plugin.md @@ -0,0 +1,63 @@ +# Plugin Component + +A wrapper that creates an iframe for a specified plugin and establishes a two-way communication channel with said plugin, allowing you to pass props (including callbacks between an app and a plugin). Note that the plugin must be built using the app-platform with entryPoints.plugin specified in the d2.config.js file. + +## Basic Usage (Defining a plugin within an app) + +Within an app you can specify a plugin (either by providing its short name `pluginShortName`, or by specifying a URL directly (`pluginSource`). If you have provided `pluginSource`, this will take precedence (Note: lookup logic is TBD? Should we allow a URL only in development mode, for example?). + +```jsx +import { Plugin } from '@dhis2/app-runtime' + +// within the app +const MyApp = () => ( + { + console.error(err) + }} + showAlertsInPlugin={true} + numberToPass={'42'} + callbackToPass={({ name }) => { + console.log(`Hi ${name}!`) + }} + /> +) +``` + +## Basic Usage (Using properties from the parent app) + +You must build your plugin with the app-platform. If you have done this, your entry component will be passed the props from the parent app. From the example above, the properties `numberToPass` and `callbackToPass` will be available in the build plugin (when it is rendered with a component). + +```jsx +// your plugin entry point (the plugin itself) + +const MyPlugin = (propsFromParent) => { + const { numberToPass, callbackToPass: sayHi } = propsFromParent + return ( + <> +

{`The meaning of life is: ${numberToPass}`}

+ + + ) +} +``` + +## Plugin Props (reserved props) + +| Name | Type | Required | Description | +| :--------------------: | :------------: | :---------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **pluginShortName** | _string_ | _required_ if `pluginSource` is not provided | The shortName of the app/plugin you wish to load (matching the result from api/apps). Used to look up the plugin entry point. If this is not provided, `pluginSource` must be provided. `pluginSource` will take precedence if provided. | +| **pluginSource** | _string_ (url) | _required_ if `pluginShortName` is not provided | The URL of the plugin. If this is not provided, `pluginShortName` must be provided. | +| **onError** | _Function_ | _optional_ | Callback function to be called when an error in the plugin triggers an error boundary. You can use this to pass an error back up to the app and create a custom handling/UX if errors occur in the plugin. In general, it is recommended that you use the plugin's built-in error boundaries | +| **showAlertsInPlugin** | _boolean_ | _optional_ | If `true`, any alerts within the plugin (defined with the `useAlert` hook) will be rendered within the iframe. By default, this is `false`. It is recommended, in general, that you do not override this and allow alerts to be hoisted up to the app level | + +## Plugin Props (custom props) + +You can specify pass any other props on the component and these will be passed down to the plugin (provided it was built with app-platform). When props are updated, they will be passed back down to the plugin. This mimics the behaviour of a normal React component, and hence you should provide stable references as needed to prevent rerendering. + +## Extended example + +See these links for an extended example of how component can be used within an [app](https://github.com/tomzemp/workingplugin/blob/plugin-wrapper-in-platform/src/App.js) and consumed within the [plugin](https://github.com/tomzemp/workingplugin/blob/plugin-wrapper-in-platform/src/Plugin.js). diff --git a/runtime/src/index.ts b/runtime/src/index.ts index 37607eb5d..aa07c14c3 100644 --- a/runtime/src/index.ts +++ b/runtime/src/index.ts @@ -22,6 +22,6 @@ export { clearSensitiveCaches, } from '@dhis2/app-service-offline' -export { PluginSender } from '@dhis2/app-service-plugin' +export { Plugin } from '@dhis2/app-service-plugin' export { Provider } from './Provider' diff --git a/services/plugin/src/PluginSender.tsx b/services/plugin/src/Plugin.tsx similarity index 99% rename from services/plugin/src/PluginSender.tsx rename to services/plugin/src/Plugin.tsx index a72663d2f..3fb2f149e 100644 --- a/services/plugin/src/PluginSender.tsx +++ b/services/plugin/src/Plugin.tsx @@ -24,7 +24,7 @@ const getPluginEntryPoint = ({ )?.pluginLaunchUrl } -export const PluginSender = ({ +export const Plugin = ({ pluginSource, pluginShortName, ...propsToPass diff --git a/services/plugin/src/index.ts b/services/plugin/src/index.ts index f6913966f..2321646ed 100644 --- a/services/plugin/src/index.ts +++ b/services/plugin/src/index.ts @@ -1 +1 @@ -export { PluginSender } from './PluginSender' +export { Plugin } from './Plugin' diff --git a/yarn.lock b/yarn.lock index a3b53d947..6d67514d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3810,15 +3810,6 @@ bcryptjs@^2.3.0: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= -belter@^1.0.41: - version "1.0.190" - resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" - integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== - dependencies: - cross-domain-safe-weakmap "^1" - cross-domain-utils "^2" - zalgo-promise "^1" - bfj@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" @@ -5025,20 +5016,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: - version "1.0.29" - resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" - integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== - dependencies: - cross-domain-utils "^2.0.0" - -cross-domain-utils@^2, cross-domain-utils@^2.0.0: - version "2.0.38" - resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" - integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== - dependencies: - zalgo-promise "^1.0.11" - cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -11531,17 +11508,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -post-robot@^10.0.46: - version "10.0.46" - resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" - integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== - dependencies: - belter "^1.0.41" - cross-domain-safe-weakmap "^1.0.1" - cross-domain-utils "^2.0.0" - universal-serialize "^1.0.4" - zalgo-promise "^1.0.3" - postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -15125,11 +15091,6 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -universal-serialize@^1.0.4: - version "1.0.10" - resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" - integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -16109,11 +16070,6 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: - version "1.0.48" - resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" - integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== - zip-stream@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" From 2480c1c6b82daaeee0ab82ef45962fbcabd0e778 Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Mon, 13 Mar 2023 15:31:03 +0100 Subject: [PATCH 10/10] fix: dependency resolution --- examples/cra/yarn.lock | 92 ++++++++++++++++++----------- examples/query-playground/yarn.lock | 84 ++++++++++++++++++-------- services/plugin/package.json | 6 +- yarn.lock | 51 +++++++++++++--- 4 files changed, 167 insertions(+), 66 deletions(-) diff --git a/examples/cra/yarn.lock b/examples/cra/yarn.lock index 08d7548ad..7fe01a14c 100644 --- a/examples/cra/yarn.lock +++ b/examples/cra/yarn.lock @@ -1054,41 +1054,33 @@ integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== "@dhis2/app-runtime@file:../../runtime": - version "3.7.0" + version "3.9.0" dependencies: - "@dhis2/app-service-alerts" "3.7.0" - "@dhis2/app-service-config" "3.7.0" - "@dhis2/app-service-data" "3.7.0" - "@dhis2/app-service-offline" "3.7.0" - "@dhis2/app-service-plugin" "3.7.0" - "@dhis2/d2-i18n" "^1.1.0" + "@dhis2/app-service-alerts" "3.9.0" + "@dhis2/app-service-config" "3.9.0" + "@dhis2/app-service-data" "3.9.0" + "@dhis2/app-service-offline" "3.9.0" -"@dhis2/app-service-alerts@3.7.0", "@dhis2/app-service-alerts@file:../../services/alerts": - version "3.7.0" +"@dhis2/app-service-alerts@3.9.0", "@dhis2/app-service-alerts@file:../../services/alerts": + version "3.9.0" -"@dhis2/app-service-config@3.7.0", "@dhis2/app-service-config@file:../../services/config": - version "3.7.0" +"@dhis2/app-service-config@3.9.0", "@dhis2/app-service-config@file:../../services/config": + version "3.9.0" -"@dhis2/app-service-data@3.7.0", "@dhis2/app-service-data@file:../../services/data": - version "3.7.0" +"@dhis2/app-service-data@3.9.0", "@dhis2/app-service-data@file:../../services/data": + version "3.9.0" dependencies: react-query "^3.13.11" -"@dhis2/app-service-offline@3.7.0", "@dhis2/app-service-offline@file:../../services/offline": - version "3.7.0" +"@dhis2/app-service-offline@3.9.0", "@dhis2/app-service-offline@file:../../services/offline": + version "3.9.0" dependencies: lodash "^4.17.21" -"@dhis2/app-service-plugin@3.7.0", "@dhis2/app-service-plugin@file:../../services/plugin": +"@dhis2/app-service-plugin@file:../../services/plugin": version "3.7.0" - -"@dhis2/d2-i18n@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@dhis2/d2-i18n/-/d2-i18n-1.1.0.tgz#ec777c5091f747e4c5aa4f9801c62ba4d1ef3d16" - integrity sha512-x3u58goDQsMfBzy50koxNrJjofJTtjRZOfz6f6Py/wMMJfp/T6vZjWMQgcfWH0JrV6d04K1RTt6bI05wqsVQvg== dependencies: - i18next "^10.3" - moment "^2.24.0" + post-robot "^10.0.46" "@hapi/address@2.x.x": version "2.1.4" @@ -2272,6 +2264,15 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +belter@^1.0.41: + version "1.0.190" + resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" + integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== + dependencies: + cross-domain-safe-weakmap "^1" + cross-domain-utils "^2" + zalgo-promise "^1" + big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -3156,6 +3157,20 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: + version "1.0.29" + resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" + integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== + dependencies: + cross-domain-utils "^2.0.0" + +cross-domain-utils@^2, cross-domain-utils@^2.0.0: + version "2.0.38" + resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" + integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== + dependencies: + zalgo-promise "^1.0.11" + cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -5162,11 +5177,6 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -i18next@^10.3: - version "10.6.0" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-10.6.0.tgz#90ffd9f9bc617f34b9a12e037260f524445f7684" - integrity sha512-ycRlN145kQf8EsyDAzMfjqv1ZT1Jwp7P2H/07bP8JLWm+7cSLD4XqlJOvq4mKVS2y2mMIy10lX9ZeYUdQ0qSRw== - iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -6896,11 +6906,6 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" -moment@^2.24.0: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -7741,6 +7746,17 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +post-robot@^10.0.46: + version "10.0.46" + resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" + integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== + dependencies: + belter "^1.0.41" + cross-domain-safe-weakmap "^1.0.1" + cross-domain-utils "^2.0.0" + universal-serialize "^1.0.4" + zalgo-promise "^1.0.3" + postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -10349,6 +10365,11 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +universal-serialize@^1.0.4: + version "1.0.10" + resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" + integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -11066,3 +11087,8 @@ yargs@^15.3.1: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^18.1.1" + +zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: + version "1.0.48" + resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" + integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== diff --git a/examples/query-playground/yarn.lock b/examples/query-playground/yarn.lock index 232e71aad..c2a4509bb 100644 --- a/examples/query-playground/yarn.lock +++ b/examples/query-playground/yarn.lock @@ -1800,33 +1800,33 @@ "@dhis2/app-service-offline" "3.8.0" "@dhis2/app-runtime@^2.2.2", "@dhis2/app-runtime@file:../../runtime": - version "3.7.0" + version "3.9.0" dependencies: - "@dhis2/app-service-alerts" "3.7.0" - "@dhis2/app-service-config" "3.7.0" - "@dhis2/app-service-data" "3.7.0" - "@dhis2/app-service-offline" "3.7.0" - "@dhis2/app-service-plugin" "3.7.0" - "@dhis2/d2-i18n" "^1.1.0" + "@dhis2/app-service-alerts" "3.9.0" + "@dhis2/app-service-config" "3.9.0" + "@dhis2/app-service-data" "3.9.0" + "@dhis2/app-service-offline" "3.9.0" -"@dhis2/app-service-alerts@3.7.0", "@dhis2/app-service-alerts@3.8.0", "@dhis2/app-service-alerts@file:../../services/alerts": - version "3.7.0" +"@dhis2/app-service-alerts@3.8.0", "@dhis2/app-service-alerts@3.9.0", "@dhis2/app-service-alerts@file:../../services/alerts": + version "3.9.0" -"@dhis2/app-service-config@3.7.0", "@dhis2/app-service-config@3.8.0", "@dhis2/app-service-config@file:../../services/config": - version "3.7.0" +"@dhis2/app-service-config@3.8.0", "@dhis2/app-service-config@3.9.0", "@dhis2/app-service-config@file:../../services/config": + version "3.9.0" -"@dhis2/app-service-data@3.7.0", "@dhis2/app-service-data@3.8.0", "@dhis2/app-service-data@file:../../services/data": - version "3.7.0" +"@dhis2/app-service-data@3.8.0", "@dhis2/app-service-data@3.9.0", "@dhis2/app-service-data@file:../../services/data": + version "3.9.0" dependencies: react-query "^3.13.11" -"@dhis2/app-service-offline@3.7.0", "@dhis2/app-service-offline@3.8.0", "@dhis2/app-service-offline@file:../../services/offline": - version "3.7.0" +"@dhis2/app-service-offline@3.8.0", "@dhis2/app-service-offline@3.9.0", "@dhis2/app-service-offline@file:../../services/offline": + version "3.9.0" dependencies: lodash "^4.17.21" -"@dhis2/app-service-plugin@3.7.0", "@dhis2/app-service-plugin@file:../../services/plugin": +"@dhis2/app-service-plugin@file:../../services/plugin": version "3.7.0" + dependencies: + post-robot "^10.0.46" "@dhis2/app-shell@5.2.0": version "5.2.0" @@ -1915,14 +1915,6 @@ i18next "^10.3" moment "^2.24.0" -"@dhis2/d2-i18n@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@dhis2/d2-i18n/-/d2-i18n-1.1.0.tgz#ec777c5091f747e4c5aa4f9801c62ba4d1ef3d16" - integrity sha512-x3u58goDQsMfBzy50koxNrJjofJTtjRZOfz6f6Py/wMMJfp/T6vZjWMQgcfWH0JrV6d04K1RTt6bI05wqsVQvg== - dependencies: - i18next "^10.3" - moment "^2.24.0" - "@dhis2/prop-types@^1.6.4": version "1.6.4" resolved "https://registry.yarnpkg.com/@dhis2/prop-types/-/prop-types-1.6.4.tgz#ec4d256c9440d4d00071524422a727c61ddaa6f6" @@ -3382,6 +3374,15 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +belter@^1.0.41: + version "1.0.190" + resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" + integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== + dependencies: + cross-domain-safe-weakmap "^1" + cross-domain-utils "^2" + zalgo-promise "^1" + big-integer@^1.6.16: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -4445,6 +4446,20 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: + version "1.0.29" + resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" + integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== + dependencies: + cross-domain-utils "^2.0.0" + +cross-domain-utils@^2, cross-domain-utils@^2.0.0: + version "2.0.38" + resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" + integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== + dependencies: + zalgo-promise "^1.0.11" + cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -9634,6 +9649,17 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +post-robot@^10.0.46: + version "10.0.46" + resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" + integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== + dependencies: + belter "^1.0.41" + cross-domain-safe-weakmap "^1.0.1" + cross-domain-utils "^2.0.0" + universal-serialize "^1.0.4" + zalgo-promise "^1.0.3" + postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -12688,6 +12714,11 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +universal-serialize@^1.0.4: + version "1.0.10" + resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" + integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -13522,6 +13553,11 @@ yargs@^15.0.0, yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.1" +zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: + version "1.0.48" + resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" + integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== + zip-stream@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b" diff --git a/services/plugin/package.json b/services/plugin/package.json index 7dc810784..f60aedb70 100644 --- a/services/plugin/package.json +++ b/services/plugin/package.json @@ -21,13 +21,15 @@ "files": [ "build/**" ], + "dependencies": { + "post-robot": "^10.0.46" + }, "peerDependencies": { "prop-types": "^15.7.2", "react": "^16.8.6", "react-dom": "^16.8.6", "@dhis2/app-service-alerts": "3.7.0", - "@dhis2/app-service-data": "3.7.0", - "post-robot": "^10.0.46" + "@dhis2/app-service-data": "3.7.0" }, "devDependencies": { "@types/post-robot": "^10.0.3" diff --git a/yarn.lock b/yarn.lock index 6276cdef2..812c0e6c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3961,22 +3961,15 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -<<<<<<< HEAD "@types/post-robot@^10.0.3": version "10.0.3" resolved "https://registry.yarnpkg.com/@types/post-robot/-/post-robot-10.0.3.tgz#d1429085f2faf4c87f841dab4e51472457edbf31" integrity sha512-y8ysuxddaG8V/oA1Ay6Err7nSADRa9Bv1rl0ZQpJ0qgdIQ7ks3CHcOsYL4qE8w75+/XYDS94dBeXDs0xexm3tA== -"@types/prettier@^2.0.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.1.tgz#374e31645d58cb18a07b3ecd8e9dede4deb2cccd" - integrity sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw== -======= "@types/prettier@^2.1.5": version "2.7.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== ->>>>>>> alpha "@types/prop-types@*": version "15.7.3" @@ -5279,6 +5272,15 @@ bcryptjs@^2.3.0: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= +belter@^1.0.41: + version "1.0.190" + resolved "https://registry.yarnpkg.com/belter/-/belter-1.0.190.tgz#491857550ef240d9c66b56fc637991f5c3089966" + integrity sha512-jz05FHrO+bwitdI6JxV5ESyRdVhTcwMWQ7L4o+q/R4LNJFQrG58sp9EiwsSjhbihhiyYFcmmCMRRagxte6igtw== + dependencies: + cross-domain-safe-weakmap "^1" + cross-domain-utils "^2" + zalgo-promise "^1" + bfj@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" @@ -6275,6 +6277,20 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" +cross-domain-safe-weakmap@^1, cross-domain-safe-weakmap@^1.0.1: + version "1.0.29" + resolved "https://registry.yarnpkg.com/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz#0847975c27d9e1cc840f24c1745311958df98022" + integrity sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA== + dependencies: + cross-domain-utils "^2.0.0" + +cross-domain-utils@^2, cross-domain-utils@^2.0.0: + version "2.0.38" + resolved "https://registry.yarnpkg.com/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz#2eaf321c4dfdb61596805ca4233fde4400cb6377" + integrity sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw== + dependencies: + zalgo-promise "^1.0.11" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -11938,6 +11954,17 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +post-robot@^10.0.46: + version "10.0.46" + resolved "https://registry.yarnpkg.com/post-robot/-/post-robot-10.0.46.tgz#39cea5b51033729390fc7c90be3285cd285f0377" + integrity sha512-EgVJiuvI4iRWDZvzObWes0X/n8olWBEJWxlSw79zmhpgkigX8UsVL4VOBhVtoJKwf0Y9qP9g2zOONw1rv80QbA== + dependencies: + belter "^1.0.41" + cross-domain-safe-weakmap "^1.0.1" + cross-domain-utils "^2.0.0" + universal-serialize "^1.0.4" + zalgo-promise "^1.0.3" + postcss-attribute-case-insensitive@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz#03d761b24afc04c09e757e92ff53716ae8ea2741" @@ -15263,6 +15290,11 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" +universal-serialize@^1.0.4: + version "1.0.10" + resolved "https://registry.yarnpkg.com/universal-serialize/-/universal-serialize-1.0.10.tgz#3279bb30f47290ea479f45135620f98fa9d3f3a6" + integrity sha512-FdouA4xSFa0fudk1+z5vLWtxZCoC0Q9lKYV3uUdFl7DttNfolmiw2ASr5ddY+/Yz6Isr68u3IqC9XMSwMP+Pow== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -16131,6 +16163,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zalgo-promise@^1, zalgo-promise@^1.0.11, zalgo-promise@^1.0.3: + version "1.0.48" + resolved "https://registry.yarnpkg.com/zalgo-promise/-/zalgo-promise-1.0.48.tgz#9e33eef502d5ed9f5a09fc5728c833c3e87afa2e" + integrity sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ== + zip-stream@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.3.tgz#26cc4bdb93641a8590dd07112e1f77af1758865b"