From 495f98f37f4f0d9668ab4ff01c47a38ef8147913 Mon Sep 17 00:00:00 2001 From: ljupcovangelski Date: Tue, 13 Apr 2021 11:22:59 +0200 Subject: [PATCH 01/50] Bump version to 0.18.0-alpha --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index c5523bd09b..2063416324 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.17.0 +0.18.0-alpha From dcc29680eec24d480260826bfb59b5b9f8ea08e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:43:12 +0200 Subject: [PATCH 02/50] Bump webpack from 5.31.2 to 5.32.0 (#1527) Bumps [webpack](https://github.com/webpack/webpack) from 5.31.2 to 5.32.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.31.2...v5.32.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f50c14e24d..844361a9d5 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "terser-webpack-plugin": "^5.1.1", "typescript": "3.7.4", "url-loader": "^4.1.1", - "webpack": "^5.31.2", + "webpack": "^5.32.0", "webpack-bundle-analyzer": "^4.4.0", "webpack-cli": "^4.6.0", "webpack-dev-server": "^3.11.2" diff --git a/yarn.lock b/yarn.lock index 892a652612..c406785e9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7673,10 +7673,10 @@ webpack-sources@^2.1.1: source-list-map "^2.0.1" source-map "^0.6.1" -webpack@^5.31.2: - version "5.31.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.31.2.tgz#40d9b9d15b7d76af73d3f1cae895b82613a544d6" - integrity sha512-0bCQe4ybo7T5Z0SC5axnIAH+1WuIdV4FwLYkaAlLtvfBhIx8bPS48WHTfiRZS1VM+pSiYt7e/rgLs3gLrH82lQ== +webpack@^5.32.0: + version "5.32.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.32.0.tgz#f013932d778dad81bd51292d0ea00865056dc22c" + integrity sha512-jB9PrNMFnPRiZGnm/j3qfNqJmP3ViRzkuQMIf8za0dgOYvSLi/cgA+UEEGvik9EQHX1KYyGng5PgBTTzGrH9xg== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.46" From a856b959e67b715389f2e7c48d7f9fa8226329eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:43:43 +0200 Subject: [PATCH 03/50] Bump @typescript-eslint/eslint-plugin from 4.21.0 to 4.22.0 (#1530) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.21.0 to 4.22.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.22.0/packages/eslint-plugin) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 60 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 844361a9d5..38f89d8ad4 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@types/lodash-es": "^4.17.4", "@types/react-window-infinite-loader": "^1.0.3", "@types/resize-observer-browser": "^0.1.5", - "@typescript-eslint/eslint-plugin": "^4.21.0", + "@typescript-eslint/eslint-plugin": "^4.22.0", "@typescript-eslint/parser": "^4.21.0", "babel-loader": "^8.0.6", "copy-webpack-plugin": "^8.1.1", diff --git a/yarn.lock b/yarn.lock index c406785e9e..6f20e57c74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1467,13 +1467,13 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== -"@typescript-eslint/eslint-plugin@^4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz#3fce2bfa76d95c00ac4f33dff369cb593aab8878" - integrity sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ== +"@typescript-eslint/eslint-plugin@^4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.22.0.tgz#3d5f29bb59e61a9dba1513d491b059e536e16dbc" + integrity sha512-U8SP9VOs275iDXaL08Ln1Fa/wLXfj5aTr/1c0t0j6CdbOnxh+TruXu1p4I0NAvdPBQgoPjHsgKn28mOi0FzfoA== dependencies: - "@typescript-eslint/experimental-utils" "4.21.0" - "@typescript-eslint/scope-manager" "4.21.0" + "@typescript-eslint/experimental-utils" "4.22.0" + "@typescript-eslint/scope-manager" "4.22.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" lodash "^4.17.15" @@ -1481,15 +1481,15 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz#0b0bb7c15d379140a660c003bdbafa71ae9134b6" - integrity sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA== +"@typescript-eslint/experimental-utils@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.22.0.tgz#68765167cca531178e7b650a53456e6e0bef3b1f" + integrity sha512-xJXHHl6TuAxB5AWiVrGhvbGL8/hbiCQ8FiWwObO3r0fnvBdrbWEDy1hlvGQOAWc6qsCWuWMKdVWlLAEMpxnddg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.21.0" - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/typescript-estree" "4.21.0" + "@typescript-eslint/scope-manager" "4.22.0" + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/typescript-estree" "4.22.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -1511,11 +1511,24 @@ "@typescript-eslint/types" "4.21.0" "@typescript-eslint/visitor-keys" "4.21.0" +"@typescript-eslint/scope-manager@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.22.0.tgz#ed411545e61161a8d702e703a4b7d96ec065b09a" + integrity sha512-OcCO7LTdk6ukawUM40wo61WdeoA7NM/zaoq1/2cs13M7GyiF+T4rxuA4xM+6LeHWjWbss7hkGXjFDRcKD4O04Q== + dependencies: + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/visitor-keys" "4.22.0" + "@typescript-eslint/types@4.21.0": version "4.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.21.0.tgz#abdc3463bda5d31156984fa5bc316789c960edef" integrity sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w== +"@typescript-eslint/types@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.22.0.tgz#0ca6fde5b68daf6dba133f30959cc0688c8dd0b6" + integrity sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA== + "@typescript-eslint/typescript-estree@4.21.0": version "4.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz#3817bd91857beeaeff90f69f1f112ea58d350b0a" @@ -1529,6 +1542,19 @@ semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz#b5d95d6d366ff3b72f5168c75775a3e46250d05c" + integrity sha512-TkIFeu5JEeSs5ze/4NID+PIcVjgoU3cUQUIZnH3Sb1cEn1lBo7StSV5bwPuJQuoxKXlzAObjYTilOEKRuhR5yg== + dependencies: + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/visitor-keys" "4.22.0" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + semver "^7.3.2" + tsutils "^3.17.1" + "@typescript-eslint/visitor-keys@4.21.0": version "4.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz#990a9acdc124331f5863c2cf21c88ba65233cd8d" @@ -1537,6 +1563,14 @@ "@typescript-eslint/types" "4.21.0" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz#169dae26d3c122935da7528c839f42a8a42f6e47" + integrity sha512-nnMu4F+s4o0sll6cBSsTeVsT4cwxB7zECK3dFxzEjPBii9xLpq4yqqsy/FU5zMfan6G60DKZSCXAa3sHJZrcYw== + dependencies: + "@typescript-eslint/types" "4.22.0" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.11.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" From f7a5c4d28fe465230cd73efdda3c26062512cded Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 14:10:11 +0200 Subject: [PATCH 04/50] Bump cypress from 7.0.1 to 7.1.0 (#1529) Bumps [cypress](https://github.com/cypress-io/cypress) from 7.0.1 to 7.1.0. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/.releaserc.base.js) - [Commits](https://github.com/cypress-io/cypress/compare/v7.0.1...v7.1.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 38f89d8ad4..7eca51ce1e 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "babel-loader": "^8.0.6", "copy-webpack-plugin": "^8.1.1", "css-loader": "^5.2.1", - "cypress": "^7.0.1", + "cypress": "^7.1.0", "eslint": "^7.24.0", "eslint-plugin-react": "^7.23.2", "file-loader": "^6.2.0", diff --git a/yarn.lock b/yarn.lock index 6f20e57c74..af75dd90b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2770,10 +2770,10 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g== -cypress@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-7.0.1.tgz#8603f84d828fd4c5462a856f55cef5642e4ce573" - integrity sha512-dMZmZDo+x3jslEQiXRGQlMmMVMhe4JpMHQ6g1unMGXTUsapU1EXlnubevmKmqZ1IQpntAlDKmx8dupOTd3oW+g== +cypress@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-7.1.0.tgz#6cb5dc22c6271a9d7a79a2477251a95afc77e531" + integrity sha512-AptQP9fVtN/FfOv8rJ9hTGJE2XQFc8saLHT38r/EeyWhzp0q/+P/DYRTDtjGZHeLTCNznAUrT4lal8jm+ouS7Q== dependencies: "@cypress/listr-verbose-renderer" "^0.4.1" "@cypress/request" "^2.88.5" From b506fb766f42aff10ad7a4c40a43ec60cef2e4b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 14:19:03 +0200 Subject: [PATCH 05/50] Bump webpack-bundle-analyzer from 4.4.0 to 4.4.1 (#1533) Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 4.4.0 to 4.4.1. - [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases) - [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v4.4.0...v4.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7eca51ce1e..3b3836c6c0 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "typescript": "3.7.4", "url-loader": "^4.1.1", "webpack": "^5.32.0", - "webpack-bundle-analyzer": "^4.4.0", + "webpack-bundle-analyzer": "^4.4.1", "webpack-cli": "^4.6.0", "webpack-dev-server": "^3.11.2" } diff --git a/yarn.lock b/yarn.lock index af75dd90b0..c1ad21c26b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7598,10 +7598,10 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -webpack-bundle-analyzer@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz#74013106e7e2b07cbd64f3a5ae847f7e814802c7" - integrity sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g== +webpack-bundle-analyzer@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz#c71fb2eaffc10a4754d7303b224adb2342069da1" + integrity sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw== dependencies: acorn "^8.0.4" acorn-walk "^8.0.0" From e880dad85743781da5efb16a6c0d9514ce90dec0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 14:19:13 +0200 Subject: [PATCH 06/50] Bump @typescript-eslint/parser from 4.21.0 to 4.22.0 (#1528) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.21.0 to 4.22.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.22.0/packages/parser) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 50 ++++++++------------------------------------------ 2 files changed, 9 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 3b3836c6c0..fb5a4b9aca 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "@types/react-window-infinite-loader": "^1.0.3", "@types/resize-observer-browser": "^0.1.5", "@typescript-eslint/eslint-plugin": "^4.22.0", - "@typescript-eslint/parser": "^4.21.0", + "@typescript-eslint/parser": "^4.22.0", "babel-loader": "^8.0.6", "copy-webpack-plugin": "^8.1.1", "css-loader": "^5.2.1", diff --git a/yarn.lock b/yarn.lock index c1ad21c26b..960fe87ce5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1493,23 +1493,15 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.21.0.tgz#a227fc2af4001668c3e3f7415d4feee5093894c1" - integrity sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA== - dependencies: - "@typescript-eslint/scope-manager" "4.21.0" - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/typescript-estree" "4.21.0" - debug "^4.1.1" - -"@typescript-eslint/scope-manager@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz#c81b661c4b8af1ec0c010d847a8f9ab76ab95b4d" - integrity sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw== +"@typescript-eslint/parser@^4.22.0": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.22.0.tgz#e1637327fcf796c641fe55f73530e90b16ac8fe8" + integrity sha512-z/bGdBJJZJN76nvAY9DkJANYgK3nlRstRRi74WHm3jjgf2I8AglrSY+6l7ogxOmn55YJ6oKZCLLy+6PW70z15Q== dependencies: - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/visitor-keys" "4.21.0" + "@typescript-eslint/scope-manager" "4.22.0" + "@typescript-eslint/types" "4.22.0" + "@typescript-eslint/typescript-estree" "4.22.0" + debug "^4.1.1" "@typescript-eslint/scope-manager@4.22.0": version "4.22.0" @@ -1519,29 +1511,11 @@ "@typescript-eslint/types" "4.22.0" "@typescript-eslint/visitor-keys" "4.22.0" -"@typescript-eslint/types@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.21.0.tgz#abdc3463bda5d31156984fa5bc316789c960edef" - integrity sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w== - "@typescript-eslint/types@4.22.0": version "4.22.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.22.0.tgz#0ca6fde5b68daf6dba133f30959cc0688c8dd0b6" integrity sha512-sW/BiXmmyMqDPO2kpOhSy2Py5w6KvRRsKZnV0c4+0nr4GIcedJwXAq+RHNK4lLVEZAJYFltnnk1tJSlbeS9lYA== -"@typescript-eslint/typescript-estree@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz#3817bd91857beeaeff90f69f1f112ea58d350b0a" - integrity sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w== - dependencies: - "@typescript-eslint/types" "4.21.0" - "@typescript-eslint/visitor-keys" "4.21.0" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - semver "^7.3.2" - tsutils "^3.17.1" - "@typescript-eslint/typescript-estree@4.22.0": version "4.22.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.22.0.tgz#b5d95d6d366ff3b72f5168c75775a3e46250d05c" @@ -1555,14 +1529,6 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@4.21.0": - version "4.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz#990a9acdc124331f5863c2cf21c88ba65233cd8d" - integrity sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w== - dependencies: - "@typescript-eslint/types" "4.21.0" - eslint-visitor-keys "^2.0.0" - "@typescript-eslint/visitor-keys@4.22.0": version "4.22.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.22.0.tgz#169dae26d3c122935da7528c839f42a8a42f6e47" From 20a6b9ec4f4dfbb3c39cb627f34d10d27b38e477 Mon Sep 17 00:00:00 2001 From: Christoph Proeschel Date: Tue, 13 Apr 2021 14:21:27 +0200 Subject: [PATCH 07/50] [#1532] Remove step 4 of airy cli installation docs (#1534) --- docs/docs/cli/introduction.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/docs/cli/introduction.md b/docs/docs/cli/introduction.md index eea7be2a75..ddb8971a3e 100644 --- a/docs/docs/cli/introduction.md +++ b/docs/docs/cli/introduction.md @@ -26,7 +26,6 @@ steps for a quick setup: - [Step 1: Check the requirements](introduction.md#step-1-check-the-requirements) - [Step 2: Install the Airy CLI](introduction.md#step-2-install-the-airy-cli) - [Step 3: Verify your installation](introduction.md#step-3-verify-your-installation) -- [Step 4: Initialize the setup](introduction.md#step-4-initialize-the-setup) ### Step 1: Check the requirements @@ -133,15 +132,6 @@ Make sure the output matches the version number you expect: airy version ``` -### Step 4: Initialize the setup - -The [airy init](cli/usage.md#init) will create a `cli.yaml` file which stores -your `apiHost` and `apiJwtToken`: - -```bash -airy init -``` - :tada: Congratulations! From 1126c9d774ad5cec9f4fcceeb2b93ad5a82f93fb Mon Sep 17 00:00:00 2001 From: Christoph Proeschel Date: Tue, 13 Apr 2021 15:17:22 +0200 Subject: [PATCH 08/50] [#1535] Release version uses correct app image tag (#1538) --- cli/pkg/cmd/create/helm.go | 6 +++++- cli/pkg/workspace/template/airy.yaml | 1 - .../charts/apps/charts/media-resolver/values.yaml | 7 ------- infrastructure/helm-chart/values.yaml | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cli/pkg/cmd/create/helm.go b/cli/pkg/cmd/create/helm.go index 2234466baa..80f1beb792 100644 --- a/cli/pkg/cmd/create/helm.go +++ b/cli/pkg/cmd/create/helm.go @@ -75,7 +75,11 @@ func (h *Helm) Setup() error { } func (h *Helm) InstallCharts(overrides []string) error { - return h.runHelm(append([]string{"install", "--values", "/apps/config/airy-config-map.yaml", "core", "/apps/helm-chart/"}, overrides...)) + return h.runHelm(append([]string{"install", + "--values", "/apps/config/airy-config-map.yaml", + "--set", "global.appImageTag=" + h.version, + "--set", "global.namespace=" + h.namespace, + "core", "/apps/helm-chart/"}, overrides...)) } func (h *Helm) runHelm(args []string) error { diff --git a/cli/pkg/workspace/template/airy.yaml b/cli/pkg/workspace/template/airy.yaml index fd647b5d90..61c8e73d88 100644 --- a/cli/pkg/workspace/template/airy.yaml +++ b/cli/pkg/workspace/template/airy.yaml @@ -1,5 +1,4 @@ global: - appImageTag: develop containerRegistry: ghcr.io/airyhq namespace: default ngrokEnabled: false diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml index 535200d921..6a2b9ac149 100644 --- a/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml @@ -1,8 +1 @@ image: media/resolver -storage: - s3: - key: "changeme" - secret: "changeme" - bucket: "changeme" - region: "changeme" - path: "path" diff --git a/infrastructure/helm-chart/values.yaml b/infrastructure/helm-chart/values.yaml index fefc8c0c04..d5b229d138 100644 --- a/infrastructure/helm-chart/values.yaml +++ b/infrastructure/helm-chart/values.yaml @@ -1,5 +1,5 @@ global: - appImageTag: latest + # required override: appImageTag containerRegistry: ghcr.io/airyhq namespace: default ngrokEnabled: false From b60f7abcaa4dc624929516a53788bcd7aa8555c2 Mon Sep 17 00:00:00 2001 From: Ljupco Vangelski Date: Wed, 14 Apr 2021 11:07:58 +0200 Subject: [PATCH 09/50] [#740] Label and introspect components (#1510) --- .../helm-chart/charts/apps/Chart.yaml | 2 +- .../api-admin/templates/deployment.yaml | 2 + .../charts/api/charts/api-admin/values.yaml | 4 +- .../charts/api-auth/templates/deployment.yaml | 2 + .../charts/api/charts/api-auth/values.yaml | 4 +- .../templates/deployment.yaml | 2 + .../api/charts/api-communication/values.yaml | 4 +- .../api-websocket/templates/deployment.yaml | 2 + .../api/charts/api-websocket/values.yaml | 2 + .../templates/deployment.yaml | 2 + .../charts/frontend-chat-plugin/values.yaml | 2 + .../frontend-ui/templates/deployment.yaml | 4 + .../frontend/charts/frontend-ui/values.yaml | 4 +- .../charts/apps/charts/integration/Chart.yaml | 5 + .../integration/charts/webhook/Chart.yaml | 5 + .../charts/webhook/templates/deployments.yaml | 174 +++++++++++++++++ .../charts/webhook/templates/services.yaml} | 14 ++ .../integration/charts/webhook/values.yaml | 5 + .../media-resolver/templates/deployment.yaml | 2 + .../apps/charts/media-resolver/values.yaml | 2 + .../chatplugin/templates/deployment.yaml | 2 + .../sources/charts/chatplugin/values.yaml | 2 + .../charts/facebook-connector/Chart.yaml | 5 - .../charts/facebook-connector/values.yaml | 1 - .../charts/facebook-events-router/Chart.yaml | 5 - .../templates/deployment.yaml | 79 -------- .../charts/facebook-events-router/values.yaml | 1 - .../deployments.yaml} | 86 ++++++++- .../templates/service.yaml | 0 .../sources/charts/facebook/values.yaml | 4 + .../google/charts/google-connector/Chart.yaml | 5 - .../templates/deployment.yaml | 87 --------- .../charts/google-connector/values.yaml | 1 - .../charts/google-events-router/Chart.yaml | 5 - .../templates/deployment.yaml | 82 -------- .../charts/google-events-router/values.yaml | 1 - .../charts/google/templates/deployments.yaml | 174 +++++++++++++++++ .../templates/service.yaml | 0 .../charts/sources/charts/google/values.yaml | 4 + .../twilio/charts/twilio-connector/Chart.yaml | 5 - .../templates/deployment.yaml | 87 --------- .../charts/twilio-connector/values.yaml | 1 - .../charts/twilio-events-router/Chart.yaml | 5 - .../templates/deployment.yaml | 82 -------- .../charts/twilio-events-router/values.yaml | 1 - .../charts/twilio/templates/deployments.yaml | 176 ++++++++++++++++++ .../templates/service.yaml | 0 .../charts/sources/charts/twilio/values.yaml | 4 + .../charts/apps/charts/webhook/Chart.yaml | 5 - .../charts/webhook-consumer/Chart.yaml | 5 - .../templates/deployment.yaml | 78 -------- .../webhook-consumer/templates/service.yaml | 13 -- .../charts/webhook-consumer/values.yaml | 1 - .../charts/webhook-publisher/Chart.yaml | 5 - .../templates/deployment.yaml | 89 --------- .../charts/webhook-publisher/values.yaml | 1 - 56 files changed, 689 insertions(+), 656 deletions(-) create mode 100644 infrastructure/helm-chart/charts/apps/charts/integration/Chart.yaml create mode 100644 infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/Chart.yaml create mode 100644 infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml rename infrastructure/helm-chart/charts/apps/charts/{webhook/charts/webhook-publisher/templates/service.yaml => integration/charts/webhook/templates/services.yaml} (55%) create mode 100644 infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/templates/deployment.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/values.yaml rename infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/{charts/facebook-connector/templates/deployment.yaml => templates/deployments.yaml} (52%) rename infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/{charts/facebook-connector => }/templates/service.yaml (100%) create mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/templates/deployment.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/templates/deployment.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/values.yaml create mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml rename infrastructure/helm-chart/charts/apps/charts/sources/charts/google/{charts/google-connector => }/templates/service.yaml (100%) create mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/templates/deployment.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/templates/deployment.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/values.yaml create mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml rename infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/{charts/twilio-connector => }/templates/service.yaml (100%) create mode 100644 infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/deployment.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/service.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/values.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/Chart.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/templates/deployment.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/values.yaml diff --git a/infrastructure/helm-chart/charts/apps/Chart.yaml b/infrastructure/helm-chart/charts/apps/Chart.yaml index 4e50d70558..b170120a89 100644 --- a/infrastructure/helm-chart/charts/apps/Chart.yaml +++ b/infrastructure/helm-chart/charts/apps/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 appVersion: "1.0" description: A Helm chart for the Airy Core application -name: apps +name: components version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml index f25e864cea..8d02705a26 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml @@ -7,6 +7,8 @@ metadata: app: api-admin type: api core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 1 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/values.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/values.yaml index ad3878079d..93d50c15b2 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/values.yaml @@ -1 +1,3 @@ -image: api/admin \ No newline at end of file +component: api-admin +mandatory: true +image: api/admin diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml index 4562a744f3..faca0f66ca 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml @@ -7,6 +7,8 @@ metadata: app: api-auth type: api core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 1 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/values.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/values.yaml index fefc71667c..1bcdbec8f8 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/values.yaml @@ -1 +1,3 @@ -image: api/auth \ No newline at end of file +component: api-auth +mandatory: true +image: api/auth diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml index 1c7df28b3d..603ac0688b 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml @@ -7,6 +7,8 @@ metadata: app: api-communication type: api core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 1 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/values.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/values.yaml index a727f3ccff..f0a957737f 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/values.yaml @@ -1 +1,3 @@ -image: api/communication \ No newline at end of file +component: api-communication +mandatory: true +image: api/communication diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml index 73329e252a..54f25cf56c 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml @@ -7,6 +7,8 @@ metadata: app: api-websocket type: api core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 1 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/values.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/values.yaml index 542d24bf95..de05c30573 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/values.yaml @@ -1 +1,3 @@ +component: api-websocket +mandatory: true image: api/websocket diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/templates/deployment.yaml index 08c8f2eb34..2924de34c7 100644 --- a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/templates/deployment.yaml @@ -7,6 +7,8 @@ metadata: app: frontend-chat-plugin type: frontend core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 1 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml index 90c3960ca1..92f3ff1ecc 100644 --- a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml @@ -1 +1,3 @@ +component: frontend-chat-plugin +mandatory: false image: frontend/chat-plugin diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/templates/deployment.yaml index 8957e7fb11..cefb0b9076 100644 --- a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/templates/deployment.yaml @@ -7,6 +7,10 @@ metadata: app: frontend-ui type: frontend core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" + annotations: + core.airy.co/config-items-mandatory: "API_HOST" spec: replicas: 1 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml index cf13af5804..d95bb6cfc4 100644 --- a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml @@ -1 +1,3 @@ -image: frontend/ui \ No newline at end of file +component: frontend-ui +mandatory: false +image: frontend/ui diff --git a/infrastructure/helm-chart/charts/apps/charts/integration/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/integration/Chart.yaml new file mode 100644 index 0000000000..d2c5a72a0d --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/integration/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the Airy Core integration components +name: integrations +version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/Chart.yaml new file mode 100644 index 0000000000..f4747e0caa --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the Webhook integration component +name: integration-webhook +version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml new file mode 100644 index 0000000000..5a68f5be18 --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml @@ -0,0 +1,174 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webhook-consumer + namespace: {{ .Values.global.namespace }} + labels: + app: webhook-consumer + type: webhook + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" +spec: + replicas: 0 + selector: + matchLabels: + app: webhook-consumer + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: webhook-consumer + spec: + containers: + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.imageConsumer }}:{{ .Values.global.appImageTag }}" + imagePullPolicy: Always + env: + - name: SERVICE_NAME + value: webhook-consumer + - name: REDIS_HOSTNAME + valueFrom: + configMapKeyRef: + name: redis-config + key: REDIS_HOSTNAME + - name: REDIS_PORT + valueFrom: + configMapKeyRef: + name: redis-config + key: REDIS_PORT + - name: WEBHOOK_NAME + valueFrom: + configMapKeyRef: + name: webhooks-config + key: NAME + livenessProbe: + httpGet: + path: /health + port: 8080 + httpHeaders: + - name: Health-Check + value: health-check + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 3 + initContainers: + - name: wait + image: busybox + command: ["/bin/sh", "/opt/provisioning/wait-for-service.sh"] + env: + - name: SERVICE_NAME + valueFrom: + configMapKeyRef: + name: redis-config + key: REDIS_HOSTNAME + - name: SERVICE_PORT + valueFrom: + configMapKeyRef: + name: redis-config + key: REDIS_PORT + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning + volumes: + - name: provisioning-scripts + configMap: + name: provisioning-scripts +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webhook-publisher + namespace: {{ .Values.global.namespace }} + labels: + app: webhook-publisher + type: webhook + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" + annotations: + core.airy.co/config-items-optional: "WEBHOOK_NAME" +spec: + replicas: 0 + selector: + matchLabels: + app: webhook-publisher + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: webhook-publisher + spec: + containers: + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.imagePublisher }}:{{ .Values.global.appImageTag }}" + imagePullPolicy: Always + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: KAFKA_SCHEMA_REGISTRY_URL + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_SCHEMA_REGISTRY_URL + - name: KAFKA_COMMIT_INTERVAL_MS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_COMMIT_INTERVAL_MS + - name: SERVICE_NAME + value: webhook-publisher + - name: REDIS_HOSTNAME + valueFrom: + configMapKeyRef: + name: redis-config + key: REDIS_HOSTNAME + - name: REDIS_PORT + valueFrom: + configMapKeyRef: + name: redis-config + key: REDIS_PORT + - name: WEBHOOK_NAME + valueFrom: + configMapKeyRef: + name: webhooks-config + key: NAME + livenessProbe: + tcpSocket: + port: 6000 + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 3 + initContainers: + - name: wait + image: busybox + command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: REPLICAS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_MINIMUM_REPLICAS + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning + volumes: + - name: provisioning-scripts + configMap: + name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/services.yaml similarity index 55% rename from infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/templates/service.yaml rename to infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/services.yaml index 3b9694c98e..3433fcd446 100644 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/templates/service.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/services.yaml @@ -1,5 +1,19 @@ apiVersion: v1 kind: Service +metadata: + name: webhook-consumer + namespace: {{ .Values.global.namespace }} +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + type: NodePort + selector: + app: webhook-consumer +--- +apiVersion: v1 +kind: Service metadata: name: webhook-publisher namespace: {{ .Values.global.namespace }} diff --git a/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/values.yaml b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/values.yaml new file mode 100644 index 0000000000..89ba74830e --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/values.yaml @@ -0,0 +1,5 @@ +component: integration-webhook +mandatory: false +enabled: false +imageConsumer: webhook/consumer +imagePublisher: webhook/publisher \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml index 54de7a956a..e09048fee0 100644 --- a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml @@ -7,6 +7,8 @@ metadata: app: media-resolver type: media core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 0 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml index 6a2b9ac149..7e0169f011 100644 --- a/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml @@ -1 +1,3 @@ +component: media-resolver +mandatory: false image: media/resolver diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml index e81fd2973f..931db96d94 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml @@ -7,6 +7,8 @@ metadata: app: sources-chatplugin type: sources core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 1 selector: diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml index 51b0f7e5e9..649fa29dff 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml @@ -1 +1,3 @@ +component: source-chat-plugin +mandatory: false image: sources/chat-plugin \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/Chart.yaml deleted file mode 100644 index d5877e85de..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Facebook Connector app -name: sources-facebook-connector -version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/values.yaml deleted file mode 100644 index e3a61e3837..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: sources/facebook-connector diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/Chart.yaml deleted file mode 100644 index dee5eb654a..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Sources Facebook Events Router app -name: sources-facebook-events-router -version: 1.0 \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/templates/deployment.yaml deleted file mode 100644 index 5433177506..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/templates/deployment.yaml +++ /dev/null @@ -1,79 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sources-facebook-events-router - namespace: {{ .Values.global.namespace }} - labels: - app: sources-facebook-events-router - type: sources - core.airy.co/managed: "true" -spec: - replicas: 0 - selector: - matchLabels: - app: sources-facebook-events-router - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - app: sources-facebook-events-router - spec: - containers: - - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" - imagePullPolicy: Always - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: KAFKA_SCHEMA_REGISTRY_URL - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_SCHEMA_REGISTRY_URL - - name: KAFKA_COMMIT_INTERVAL_MS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_COMMIT_INTERVAL_MS - - name: FACEBOOK_APP_ID - valueFrom: - configMapKeyRef: - name: sources-facebook - key: FACEBOOK_APP_ID - - name: SERVICE_NAME - value: facebook-events-router - livenessProbe: - tcpSocket: - port: 6000 - initialDelaySeconds: 60 - periodSeconds: 10 - failureThreshold: 3 - initContainers: - - name: wait - image: busybox - command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: REPLICAS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_MINIMUM_REPLICAS - volumeMounts: - - name: provisioning-scripts - mountPath: /opt/provisioning - volumes: - - name: provisioning-scripts - configMap: - name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/values.yaml deleted file mode 100644 index bd150cabec..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-events-router/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: sources/facebook-events-router \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml similarity index 52% rename from infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/templates/deployment.yaml rename to infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml index 9cb63b85f2..e1df570c9c 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml @@ -7,6 +7,8 @@ metadata: app: sources-facebook-connector type: sources core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" spec: replicas: 0 selector: @@ -24,7 +26,7 @@ spec: spec: containers: - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" + image: "{{ .Values.global.containerRegistry}}/{{ .Values.imageConnector }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always envFrom: - configMapRef: @@ -92,3 +94,85 @@ spec: - name: provisioning-scripts configMap: name: provisioning-scripts +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sources-facebook-events-router + namespace: {{ .Values.global.namespace }} + labels: + app: sources-facebook-events-router + type: sources + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" +spec: + replicas: 0 + selector: + matchLabels: + app: sources-facebook-events-router + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: sources-facebook-events-router + spec: + containers: + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.imageEventsRouter }}:{{ .Values.global.appImageTag }}" + imagePullPolicy: Always + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: KAFKA_SCHEMA_REGISTRY_URL + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_SCHEMA_REGISTRY_URL + - name: KAFKA_COMMIT_INTERVAL_MS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_COMMIT_INTERVAL_MS + - name: FACEBOOK_APP_ID + valueFrom: + configMapKeyRef: + name: sources-facebook + key: FACEBOOK_APP_ID + - name: SERVICE_NAME + value: facebook-events-router + livenessProbe: + tcpSocket: + port: 6000 + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 3 + initContainers: + - name: wait + image: busybox + command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: REPLICAS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_MINIMUM_REPLICAS + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning + volumes: + - name: provisioning-scripts + configMap: + name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/service.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/charts/facebook-connector/templates/service.yaml rename to infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/service.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml new file mode 100644 index 0000000000..57160ff90f --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml @@ -0,0 +1,4 @@ +component: source-facebook +mandatory: false +imageConnector: sources/facebook-connector +imageEventsRouter: sources/facebook-events-router diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/Chart.yaml deleted file mode 100644 index bf7c54fab6..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: '1.0' -description: A Helm chart for the Google Connector -name: sources-google-connector -version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/templates/deployment.yaml deleted file mode 100644 index f8b159fbf3..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/templates/deployment.yaml +++ /dev/null @@ -1,87 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sources-google-connector - namespace: {{ .Values.global.namespace }} - labels: - app: sources-google-connector - type: sources - core.airy.co/managed: "true" -spec: - replicas: 0 - selector: - matchLabels: - app: sources-google-connector - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - app: sources-google-connector - spec: - containers: - - name: app - image: '{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}' - imagePullPolicy: Always - envFrom: - - configMapRef: - name: api-config - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: KAFKA_SCHEMA_REGISTRY_URL - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_SCHEMA_REGISTRY_URL - - name: KAFKA_COMMIT_INTERVAL_MS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_COMMIT_INTERVAL_MS - - name: GOOGLE_SA_FILE - valueFrom: - configMapKeyRef: - name: sources-google - key: GOOGLE_SA_FILE - - name: GOOGLE_PARTNER_KEY - valueFrom: - configMapKeyRef: - name: sources-google - key: GOOGLE_PARTNER_KEY - livenessProbe: - httpGet: - path: /actuator/health - port: 8080 - httpHeaders: - - name: Health-Check - value: health-check - initialDelaySeconds: 60 - initContainers: - - name: wait - image: busybox - command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: REPLICAS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_MINIMUM_REPLICAS - volumeMounts: - - name: provisioning-scripts - mountPath: /opt/provisioning - volumes: - - name: provisioning-scripts - configMap: - name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/values.yaml deleted file mode 100644 index 9bdfe99536..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: sources/google-connector diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/Chart.yaml deleted file mode 100644 index db60bfa5a1..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Sources Google Events Router app -name: sources-google-events-router -version: 1.0 \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/templates/deployment.yaml deleted file mode 100644 index 3441667ccc..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/templates/deployment.yaml +++ /dev/null @@ -1,82 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sources-google-events-router - namespace: {{ .Values.global.namespace }} - labels: - app: sources-google-events-router - type: sources - core.airy.co/managed: "true" -spec: - replicas: 0 - selector: - matchLabels: - app: sources-google-events-router - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - app: sources-google-events-router - spec: - containers: - - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" - imagePullPolicy: Always - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: KAFKA_SCHEMA_REGISTRY_URL - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_SCHEMA_REGISTRY_URL - - name: KAFKA_COMMIT_INTERVAL_MS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_COMMIT_INTERVAL_MS - - name: GOOGLE_SA_FILE - valueFrom: - configMapKeyRef: - name: sources-google - key: GOOGLE_SA_FILE - - name: GOOGLE_PARTNER_KEY - valueFrom: - configMapKeyRef: - name: sources-google - key: GOOGLE_PARTNER_KEY - livenessProbe: - tcpSocket: - port: 6000 - initialDelaySeconds: 60 - periodSeconds: 10 - failureThreshold: 3 - initContainers: - - name: wait - image: busybox - command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: REPLICAS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_MINIMUM_REPLICAS - volumeMounts: - - name: provisioning-scripts - mountPath: /opt/provisioning - volumes: - - name: provisioning-scripts - configMap: - name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/values.yaml deleted file mode 100644 index ffdd0256ff..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-events-router/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: sources/google-events-router \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml new file mode 100644 index 0000000000..9b3697b133 --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml @@ -0,0 +1,174 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sources-google-connector + namespace: {{ .Values.global.namespace }} + labels: + app: sources-google-connector + type: sources + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" +spec: + replicas: 0 + selector: + matchLabels: + app: sources-google-connector + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: sources-google-connector + spec: + containers: + - name: app + image: '{{ .Values.global.containerRegistry}}/{{ .Values.imageConnector }}:{{ .Values.global.appImageTag }}' + imagePullPolicy: Always + envFrom: + - configMapRef: + name: api-config + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: KAFKA_SCHEMA_REGISTRY_URL + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_SCHEMA_REGISTRY_URL + - name: KAFKA_COMMIT_INTERVAL_MS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_COMMIT_INTERVAL_MS + - name: GOOGLE_SA_FILE + valueFrom: + configMapKeyRef: + name: sources-google + key: GOOGLE_SA_FILE + - name: GOOGLE_PARTNER_KEY + valueFrom: + configMapKeyRef: + name: sources-google + key: GOOGLE_PARTNER_KEY + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + httpHeaders: + - name: Health-Check + value: health-check + initialDelaySeconds: 60 + initContainers: + - name: wait + image: busybox + command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: REPLICAS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_MINIMUM_REPLICAS + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning + volumes: + - name: provisioning-scripts + configMap: + name: provisioning-scripts +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sources-google-events-router + namespace: {{ .Values.global.namespace }} + labels: + app: sources-google-events-router + type: sources + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" +spec: + replicas: 0 + selector: + matchLabels: + app: sources-google-events-router + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: sources-google-events-router + spec: + containers: + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.imageEventsRouter }}:{{ .Values.global.appImageTag }}" + imagePullPolicy: Always + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: KAFKA_SCHEMA_REGISTRY_URL + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_SCHEMA_REGISTRY_URL + - name: KAFKA_COMMIT_INTERVAL_MS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_COMMIT_INTERVAL_MS + - name: GOOGLE_SA_FILE + valueFrom: + configMapKeyRef: + name: sources-google + key: GOOGLE_SA_FILE + - name: GOOGLE_PARTNER_KEY + valueFrom: + configMapKeyRef: + name: sources-google + key: GOOGLE_PARTNER_KEY + livenessProbe: + tcpSocket: + port: 6000 + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 3 + initContainers: + - name: wait + image: busybox + command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: REPLICAS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_MINIMUM_REPLICAS + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning + volumes: + - name: provisioning-scripts + configMap: + name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/service.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/sources/charts/google/charts/google-connector/templates/service.yaml rename to infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/service.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml new file mode 100644 index 0000000000..a9f55b95e8 --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml @@ -0,0 +1,4 @@ +component: source-google +mandatory: false +imageConnector: sources/google-connector +imageEventsRouter: sources/google-events-router diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/Chart.yaml deleted file mode 100644 index 18a2f56479..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Twilio Connector -name: sources-twilio-connector -version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/templates/deployment.yaml deleted file mode 100644 index 19942b1d5f..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/templates/deployment.yaml +++ /dev/null @@ -1,87 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sources-twilio-connector - namespace: {{ .Values.global.namespace }} - labels: - app: sources-twilio-connector - type: sources - core.airy.co/managed: "true" -spec: - replicas: 0 - selector: - matchLabels: - app: sources-twilio-connector - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - app: sources-twilio-connector - spec: - containers: - - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" - imagePullPolicy: Always - envFrom: - - configMapRef: - name: api-config - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: KAFKA_SCHEMA_REGISTRY_URL - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_SCHEMA_REGISTRY_URL - - name: KAFKA_COMMIT_INTERVAL_MS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_COMMIT_INTERVAL_MS - - name: TWILIO_AUTH_TOKEN - valueFrom: - configMapKeyRef: - name: sources-twilio - key: TWILIO_AUTH_TOKEN - - name: TWILIO_ACCOUNT_SID - valueFrom: - configMapKeyRef: - name: sources-twilio - key: TWILIO_ACCOUNT_SID - livenessProbe: - httpGet: - path: /actuator/health - port: 8080 - httpHeaders: - - name: Health-Check - value: health-check - initialDelaySeconds: 60 - initContainers: - - name: wait - image: busybox - command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: REPLICAS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_MINIMUM_REPLICAS - volumeMounts: - - name: provisioning-scripts - mountPath: /opt/provisioning - volumes: - - name: provisioning-scripts - configMap: - name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/values.yaml deleted file mode 100644 index 9dbbc94cb5..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: sources/twilio-connector diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/Chart.yaml deleted file mode 100644 index 5ebfe66da0..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Sources Twilio Events Router app -name: sources-twilio-events-router -version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/templates/deployment.yaml deleted file mode 100644 index a6eee5e113..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/templates/deployment.yaml +++ /dev/null @@ -1,82 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sources-twilio-events-router - namespace: {{ .Values.global.namespace }} - labels: - app: sources-twilio-events-router - type: sources - core.airy.co/managed: "true" -spec: - replicas: 0 - selector: - matchLabels: - app: sources-twilio-events-router - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - app: sources-twilio-events-router - spec: - containers: - - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" - imagePullPolicy: Always - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: KAFKA_SCHEMA_REGISTRY_URL - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_SCHEMA_REGISTRY_URL - - name: KAFKA_COMMIT_INTERVAL_MS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_COMMIT_INTERVAL_MS - - name: TWILIO_AUTH_TOKEN - valueFrom: - configMapKeyRef: - name: sources-twilio - key: TWILIO_AUTH_TOKEN - - name: TWILIO_ACCOUNT_SID - valueFrom: - configMapKeyRef: - name: sources-twilio - key: TWILIO_ACCOUNT_SID - livenessProbe: - tcpSocket: - port: 6000 - initialDelaySeconds: 60 - periodSeconds: 10 - failureThreshold: 3 - initContainers: - - name: wait - image: busybox - command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: REPLICAS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_MINIMUM_REPLICAS - volumeMounts: - - name: provisioning-scripts - mountPath: /opt/provisioning - volumes: - - name: provisioning-scripts - configMap: - name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/values.yaml deleted file mode 100644 index 6720f59d37..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-events-router/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: sources/twilio-events-router diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml new file mode 100644 index 0000000000..03a7e67449 --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml @@ -0,0 +1,176 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sources-twilio-connector + namespace: {{ .Values.global.namespace }} + labels: + app: sources-twilio-connector + type: sources + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" +spec: + replicas: 0 + selector: + matchLabels: + app: sources-twilio-connector + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: sources-twilio-connector + spec: + containers: + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.imageConnector }}:{{ .Values.global.appImageTag }}" + imagePullPolicy: Always + envFrom: + - configMapRef: + name: api-config + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: KAFKA_SCHEMA_REGISTRY_URL + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_SCHEMA_REGISTRY_URL + - name: KAFKA_COMMIT_INTERVAL_MS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_COMMIT_INTERVAL_MS + - name: TWILIO_AUTH_TOKEN + valueFrom: + configMapKeyRef: + name: sources-twilio + key: TWILIO_AUTH_TOKEN + - name: TWILIO_ACCOUNT_SID + valueFrom: + configMapKeyRef: + name: sources-twilio + key: TWILIO_ACCOUNT_SID + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + httpHeaders: + - name: Health-Check + value: health-check + initialDelaySeconds: 60 + initContainers: + - name: wait + image: busybox + command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: REPLICAS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_MINIMUM_REPLICAS + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning + volumes: + - name: provisioning-scripts + configMap: + name: provisioning-scripts +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sources-twilio-events-router + namespace: {{ .Values.global.namespace }} + labels: + app: sources-twilio-events-router + type: sources + core.airy.co/managed: "true" + core.airy.co/mandatory: "{{ .Values.mandatory }}" + core.airy.co/component: "{{ .Values.component }}" + annotations: + core.airy.co/config-items-mandatory: "TWILIO_AUTH_TOKEN TWILIO_ACCOUNT_SID" +spec: + replicas: 0 + selector: + matchLabels: + app: sources-twilio-events-router + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: sources-twilio-events-router + spec: + containers: + - name: app + image: "{{ .Values.global.containerRegistry}}/{{ .Values.imageEventsRouter }}:{{ .Values.global.appImageTag }}" + imagePullPolicy: Always + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: KAFKA_SCHEMA_REGISTRY_URL + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_SCHEMA_REGISTRY_URL + - name: KAFKA_COMMIT_INTERVAL_MS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_COMMIT_INTERVAL_MS + - name: TWILIO_AUTH_TOKEN + valueFrom: + configMapKeyRef: + name: sources-twilio + key: TWILIO_AUTH_TOKEN + - name: TWILIO_ACCOUNT_SID + valueFrom: + configMapKeyRef: + name: sources-twilio + key: TWILIO_ACCOUNT_SID + livenessProbe: + tcpSocket: + port: 6000 + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 3 + initContainers: + - name: wait + image: busybox + command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] + env: + - name: KAFKA_BROKERS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_BROKERS + - name: REPLICAS + valueFrom: + configMapKeyRef: + name: kafka-config + key: KAFKA_MINIMUM_REPLICAS + volumeMounts: + - name: provisioning-scripts + mountPath: /opt/provisioning + volumes: + - name: provisioning-scripts + configMap: + name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/service.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/charts/twilio-connector/templates/service.yaml rename to infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/service.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml new file mode 100644 index 0000000000..ac5e929b36 --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml @@ -0,0 +1,4 @@ +component: source-twilio +mandatory: false +imageConnector: sources/twilio-connector +imageEventsRouter: sources/twilio-events-router diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/Chart.yaml deleted file mode 100644 index fa6cf0a483..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Airy Core webhook component -name: webhook -version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/Chart.yaml deleted file mode 100644 index 5d824dfcc1..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Webhook Consumer app -name: webhook-consumer -version: 1.0 \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/deployment.yaml deleted file mode 100644 index 6cfc2d389f..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/deployment.yaml +++ /dev/null @@ -1,78 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: webhook-consumer - namespace: {{ .Values.global.namespace }} - labels: - app: webhook-consumer - type: webhook - core.airy.co/managed: "true" -spec: - replicas: 0 - selector: - matchLabels: - app: webhook-consumer - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - app: webhook-consumer - spec: - containers: - - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" - imagePullPolicy: Always - env: - - name: SERVICE_NAME - value: webhook-consumer - - name: REDIS_HOSTNAME - valueFrom: - configMapKeyRef: - name: redis-config - key: REDIS_HOSTNAME - - name: REDIS_PORT - valueFrom: - configMapKeyRef: - name: redis-config - key: REDIS_PORT - - name: WEBHOOK_NAME - valueFrom: - configMapKeyRef: - name: webhooks-config - key: NAME - livenessProbe: - httpGet: - path: /health - port: 8080 - httpHeaders: - - name: Health-Check - value: health-check - initialDelaySeconds: 60 - periodSeconds: 10 - failureThreshold: 3 - initContainers: - - name: wait - image: busybox - command: ["/bin/sh", "/opt/provisioning/wait-for-service.sh"] - env: - - name: SERVICE_NAME - valueFrom: - configMapKeyRef: - name: redis-config - key: REDIS_HOSTNAME - - name: SERVICE_PORT - valueFrom: - configMapKeyRef: - name: redis-config - key: REDIS_PORT - volumeMounts: - - name: provisioning-scripts - mountPath: /opt/provisioning - volumes: - - name: provisioning-scripts - configMap: - name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/service.yaml deleted file mode 100644 index 4cdb451059..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/templates/service.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: webhook-consumer - namespace: {{ .Values.global.namespace }} -spec: - ports: - - port: 80 - targetPort: 8080 - protocol: TCP - type: NodePort - selector: - app: webhook-consumer diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/values.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/values.yaml deleted file mode 100644 index dc75dbee55..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-consumer/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: webhook/consumer \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/Chart.yaml deleted file mode 100644 index 71d1a3a335..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -appVersion: "1.0" -description: A Helm chart for the Webhook Publisher app -name: webhook-publisher -version: 1.0 \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/templates/deployment.yaml deleted file mode 100644 index 669a897b82..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/templates/deployment.yaml +++ /dev/null @@ -1,89 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: webhook-publisher - namespace: {{ .Values.global.namespace }} - labels: - app: webhook-publisher - type: webhook - core.airy.co/managed: "true" -spec: - replicas: 0 - selector: - matchLabels: - app: webhook-publisher - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 1 - type: RollingUpdate - template: - metadata: - labels: - app: webhook-publisher - spec: - containers: - - name: app - image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" - imagePullPolicy: Always - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: KAFKA_SCHEMA_REGISTRY_URL - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_SCHEMA_REGISTRY_URL - - name: KAFKA_COMMIT_INTERVAL_MS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_COMMIT_INTERVAL_MS - - name: SERVICE_NAME - value: webhook-publisher - - name: REDIS_HOSTNAME - valueFrom: - configMapKeyRef: - name: redis-config - key: REDIS_HOSTNAME - - name: REDIS_PORT - valueFrom: - configMapKeyRef: - name: redis-config - key: REDIS_PORT - - name: WEBHOOK_NAME - valueFrom: - configMapKeyRef: - name: webhooks-config - key: NAME - livenessProbe: - tcpSocket: - port: 6000 - initialDelaySeconds: 60 - periodSeconds: 10 - failureThreshold: 3 - initContainers: - - name: wait - image: busybox - command: ["/bin/sh", "/opt/provisioning/wait-for-minimum-kafkas.sh"] - env: - - name: KAFKA_BROKERS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_BROKERS - - name: REPLICAS - valueFrom: - configMapKeyRef: - name: kafka-config - key: KAFKA_MINIMUM_REPLICAS - volumeMounts: - - name: provisioning-scripts - mountPath: /opt/provisioning - volumes: - - name: provisioning-scripts - configMap: - name: provisioning-scripts diff --git a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/values.yaml b/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/values.yaml deleted file mode 100644 index bf1aac3b89..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/webhook/charts/webhook-publisher/values.yaml +++ /dev/null @@ -1 +0,0 @@ -image: webhook/publisher \ No newline at end of file From 5c416a793b812b2765c4f20804eb23457756d823 Mon Sep 17 00:00:00 2001 From: Thorsten Date: Wed, 14 Apr 2021 12:26:39 +0200 Subject: [PATCH 10/50] [#1540] Solved return issue and fixed sendButton color (#1542) --- frontend/ui/src/pages/Inbox/MessageInput/index.tsx | 4 +++- frontend/ui/src/pages/Inbox/Messenger/MessageList/index.tsx | 2 +- lib/typescript/render/components/Text/index.module.scss | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/ui/src/pages/Inbox/MessageInput/index.tsx b/frontend/ui/src/pages/Inbox/MessageInput/index.tsx index 01669bafa9..c763bb4eb0 100644 --- a/frontend/ui/src/pages/Inbox/MessageInput/index.tsx +++ b/frontend/ui/src/pages/Inbox/MessageInput/index.tsx @@ -331,7 +331,9 @@ const MessageInput = (props: MessageInputProps & ConnectedProps & { - showSuggestedReplies: (sugggestions: Suggestions) => void; + showSuggestedReplies: (suggestions: Suggestions) => void; }; const mapStateToProps = (state: StateModel, ownProps: ConversationRouteProps) => { diff --git a/lib/typescript/render/components/Text/index.module.scss b/lib/typescript/render/components/Text/index.module.scss index a8dd319e21..4976e0a905 100644 --- a/lib/typescript/render/components/Text/index.module.scss +++ b/lib/typescript/render/components/Text/index.module.scss @@ -5,6 +5,7 @@ overflow-wrap: break-word; max-width: min(500px, 100%); word-break: break-word; + white-space: pre-wrap; padding: 10px; margin-top: 5px; border-radius: 8px; From ccb50854381f7185841fd9204db7a995195d46ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:41:38 +0200 Subject: [PATCH 11/50] Bump react-modal from 3.12.1 to 3.13.1 (#1545) Bumps [react-modal](https://github.com/reactjs/react-modal) from 3.12.1 to 3.13.1. - [Release notes](https://github.com/reactjs/react-modal/releases) - [Changelog](https://github.com/reactjs/react-modal/blob/master/CHANGELOG.md) - [Commits](https://github.com/reactjs/react-modal/compare/v3.12.1...v3.13.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index fb5a4b9aca..a3b6437dc9 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "react-dom": "16.14.0", "react-facebook-login": "^4.1.1", "react-markdown": "^5.0.3", - "react-modal": "^3.12.1", + "react-modal": "^3.13.1", "react-redux": "7.2.3", "react-router-dom": "5.2.0", "react-window": "1.8.6", diff --git a/yarn.lock b/yarn.lock index 960fe87ce5..8dbf5c22a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6062,10 +6062,10 @@ react-markdown@^5.0.3: unist-util-visit "^2.0.0" xtend "^4.0.1" -react-modal@^3.12.1: - version "3.12.1" - resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.12.1.tgz#38c33f70d81c33d02ff1ed115530443a3dc2afd3" - integrity sha512-WGuXn7Fq31PbFJwtWmOk+jFtGC7E9tJVbFX0lts8ZoS5EPi9+WWylUJWLKKVm3H4GlQ7ZxY7R6tLlbSIBQ5oZA== +react-modal@^3.13.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.13.1.tgz#a02dce63bbfee7582936f1e9835d518ef8f56453" + integrity sha512-m6yXK7I4YKssQnsjHK7xITSXy2O81BSOHOsg0/uWAsdKtuT9HF2tdoYhRuxNNQg2V+LgepsoHUPJKS8m6no+eg== dependencies: exenv "^1.2.0" prop-types "^15.5.10" From 120909db7cbee14af731dea26b052ead4ce0760c Mon Sep 17 00:00:00 2001 From: Paulo Diniz Date: Wed, 14 Apr 2021 16:28:27 +0200 Subject: [PATCH 12/50] [#740] - Adding k8s endpoint to airy controller (#1546) --- infrastructure/controller/BUILD | 1 + infrastructure/controller/main.go | 3 ++ infrastructure/controller/pkg/endpoints/BUILD | 16 +++++++ .../controller/pkg/endpoints/endpoints.go | 46 +++++++++++++++++++ .../charts/controller/templates/service.yaml | 14 ++++++ 5 files changed, 80 insertions(+) create mode 100644 infrastructure/controller/pkg/endpoints/BUILD create mode 100644 infrastructure/controller/pkg/endpoints/endpoints.go create mode 100644 infrastructure/helm-chart/charts/controller/templates/service.yaml diff --git a/infrastructure/controller/BUILD b/infrastructure/controller/BUILD index 0ea9c5dddf..feeb72fdaf 100644 --- a/infrastructure/controller/BUILD +++ b/infrastructure/controller/BUILD @@ -12,6 +12,7 @@ go_library( visibility = ["//visibility:private"], deps = [ "//infrastructure/controller/pkg/configmap-controller", + "//infrastructure/controller/pkg/endpoints", "@io_k8s_api//core/v1:go_default_library", "@io_k8s_client_go//kubernetes:go_default_library", "@io_k8s_client_go//tools/clientcmd:go_default_library", diff --git a/infrastructure/controller/main.go b/infrastructure/controller/main.go index 1c49b437cc..4592b729f0 100644 --- a/infrastructure/controller/main.go +++ b/infrastructure/controller/main.go @@ -11,6 +11,7 @@ package main import ( "flag" cm "github.com/airyhq/airy/infrastructure/controller/pkg/configmap-controller" + endpoints "github.com/airyhq/airy/infrastructure/controller/pkg/endpoints" v1 "k8s.io/api/core/v1" "os" @@ -58,6 +59,8 @@ func main() { defer close(stop) go configMapController.Run(1, stop) + go endpoints.Serve(clientSet) + // Wait forever select {} } diff --git a/infrastructure/controller/pkg/endpoints/BUILD b/infrastructure/controller/pkg/endpoints/BUILD new file mode 100644 index 0000000000..c5b3d49428 --- /dev/null +++ b/infrastructure/controller/pkg/endpoints/BUILD @@ -0,0 +1,16 @@ +load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "endpoints", + srcs = ["endpoints.go"], + importpath = "github.com/airyhq/airy/infrastructure/controller/pkg/endpoints", + visibility = ["//visibility:public"], + deps = [ + "@io_k8s_api//apps/v1:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_client_go//kubernetes:go_default_library", + ], +) + +check_pkg(name = "buildifier") diff --git a/infrastructure/controller/pkg/endpoints/endpoints.go b/infrastructure/controller/pkg/endpoints/endpoints.go new file mode 100644 index 0000000000..107cf6ba8c --- /dev/null +++ b/infrastructure/controller/pkg/endpoints/endpoints.go @@ -0,0 +1,46 @@ +package endpoints + +import ( + "context" + "encoding/json" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "log" + "net/http" +) + +type Server struct { + clientSet *kubernetes.Clientset +} + +type ComponentsResponse struct { + Components []string `json:"components"` +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + deployments, _ := s.clientSet.AppsV1().Deployments("default").List(context.TODO(), v1.ListOptions{ + LabelSelector: "core.airy.co/component", + }) + + componentsMap := make(map[string]int) + for _, deployment := range deployments.Items { + component := deployment.ObjectMeta.Labels["core.airy.co/component"] + componentsMap[component] = 1 + } + + components := make([]string, 0, len(componentsMap)) + for component := range componentsMap { + components = append(components, component) + } + + resp, _ := json.Marshal(&ComponentsResponse{Components: components}) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(resp) +} + +func Serve(clientSet *kubernetes.Clientset) { + s := &Server{clientSet: clientSet} + http.Handle("/components", s) + log.Fatal(http.ListenAndServe(":8080", nil)) +} diff --git a/infrastructure/helm-chart/charts/controller/templates/service.yaml b/infrastructure/helm-chart/charts/controller/templates/service.yaml new file mode 100644 index 0000000000..4c6c76c112 --- /dev/null +++ b/infrastructure/helm-chart/charts/controller/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: airy-controller + namespace: {{ .Values.global.namespace }} +spec: + ports: + - name: web + port: 80 + targetPort: 8080 + protocol: TCP + type: NodePort + selector: + app: airy-controller From 2651b59fe5311043cd50b7241d1041232d4ad171 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Apr 2021 10:56:32 +0200 Subject: [PATCH 13/50] Bump webpack from 5.32.0 to 5.33.2 (#1551) Bumps [webpack](https://github.com/webpack/webpack) from 5.32.0 to 5.33.2. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.32.0...v5.33.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a3b6437dc9..a61983c48a 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "terser-webpack-plugin": "^5.1.1", "typescript": "3.7.4", "url-loader": "^4.1.1", - "webpack": "^5.32.0", + "webpack": "^5.33.2", "webpack-bundle-analyzer": "^4.4.1", "webpack-cli": "^4.6.0", "webpack-dev-server": "^3.11.2" diff --git a/yarn.lock b/yarn.lock index 8dbf5c22a2..70a36ee37b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7673,10 +7673,10 @@ webpack-sources@^2.1.1: source-list-map "^2.0.1" source-map "^0.6.1" -webpack@^5.32.0: - version "5.32.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.32.0.tgz#f013932d778dad81bd51292d0ea00865056dc22c" - integrity sha512-jB9PrNMFnPRiZGnm/j3qfNqJmP3ViRzkuQMIf8za0dgOYvSLi/cgA+UEEGvik9EQHX1KYyGng5PgBTTzGrH9xg== +webpack@^5.33.2: + version "5.33.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.33.2.tgz#c049717c9b038febf5a72fd2f53319ad59a8c1fc" + integrity sha512-X4b7F1sYBmJx8mlh2B7mV5szEkE0jYNJ2y3akgAP0ERi0vLCG1VvdsIxt8lFd4st6SUy0lf7W0CCQS566MBpJg== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.46" From 057eefec78b55e295fbfd642ce2aeb9441af7626 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Apr 2021 11:13:49 +0200 Subject: [PATCH 14/50] Bump @types/node from 14.14.37 to 14.14.39 (#1553) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.37 to 14.14.39. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a61983c48a..9728d4e9f1 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@crello/react-lottie": "^0.0.11", "@reduxjs/toolkit": "^1.5.1", "@stomp/stompjs": "^6.1.0", - "@types/node": "14.14.37", + "@types/node": "14.14.39", "@types/react": "16.9.34", "@types/react-dom": "16.9.2", "@types/react-redux": "7.1.16", diff --git a/yarn.lock b/yarn.lock index 70a36ee37b..74e34e1134 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1351,10 +1351,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== -"@types/node@*", "@types/node@14.14.37", "@types/node@^14.14.31": - version "14.14.37" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e" - integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw== +"@types/node@*", "@types/node@14.14.39", "@types/node@^14.14.31": + version "14.14.39" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.39.tgz#9ef394d4eb52953d2890e4839393c309aa25d2d1" + integrity sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw== "@types/node@^10.1.0": version "10.17.55" From 2a4e3c9f1f42cadbd76c35df1bb780ef2cc4e7a5 Mon Sep 17 00:00:00 2001 From: Kazeem Adetunji Date: Thu, 15 Apr 2021 12:26:18 +0200 Subject: [PATCH 15/50] [#1503] Cypress test to end a conversation in chatplugin (#1543) Closes #1543 --- frontend/chat-plugin/handles/index.ts | 3 ++ .../airyRenderProps/AiryHeaderBar/index.tsx | 7 ++++- .../chat-plugin/src/components/chat/index.tsx | 7 +++-- .../src/components/newConversation/index.tsx | 7 ++++- .../chat-plugin/end_conversation.spec.ts | 31 +++++++++++++++++++ .../chat-plugin/websocket_test.spec.ts | 1 - integration/cypress.json | 1 + .../ui/send_chatplugin_message.spec.ts | 2 +- 8 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 integration/chat-plugin/end_conversation.spec.ts diff --git a/frontend/chat-plugin/handles/index.ts b/frontend/chat-plugin/handles/index.ts index 0f7a74aedc..5dda6f6533 100644 --- a/frontend/chat-plugin/handles/index.ts +++ b/frontend/chat-plugin/handles/index.ts @@ -2,3 +2,6 @@ export const cyBubble = 'bubble'; export const cyInputbarTextarea = 'inputbarTextarea'; export const cyInputbarButton = 'inputbarButton'; export const cyChatPluginMessageList = 'chatPluginMessageList'; +export const cyChatPluginHeaderBarCloseButton = 'chatPluginHeaderBarCloseButton'; +export const cyChatPluginEndChatModalButton = 'chatPluginEndChatModalButton'; +export const cyChatPluginStartNewConversation = 'chatPluginStartNewConversation'; diff --git a/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx b/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx index bbeaf665cf..f2f9f707b3 100644 --- a/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx +++ b/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx @@ -3,6 +3,7 @@ import {Config} from '../../App'; import style from './index.module.scss'; import {ReactComponent as CloseButton} from 'assets/images/icons/close.svg'; import {ReactComponent as MinimizeButton} from 'assets/images/icons/minimize-button.svg'; +import {cyChatPluginHeaderBarCloseButton} from 'chat-plugin-handles'; type AiryHeaderBarProps = { toggleHideChat: () => void; @@ -35,7 +36,11 @@ const AiryHeaderBar = (props: AiryHeaderBarProps) => { - diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index 188227dd96..e4beade46a 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -18,7 +18,7 @@ import {SourceMessage, CommandUnion} from 'render'; import {MessageInfoWrapper} from 'render/components/MessageInfoWrapper'; /* eslint-disable @typescript-eslint/no-var-requires */ const camelcaseKeys = require('camelcase-keys'); -import {cyBubble, cyChatPluginMessageList} from 'chat-plugin-handles'; +import {cyBubble, cyChatPluginMessageList, cyChatPluginEndChatModalButton} from 'chat-plugin-handles'; import {getResumeTokenFromStorage, resetStorage} from '../../storage'; import {ModalDialogue} from '../../components/modal'; import NewConversation from '../../components/newConversation'; @@ -243,7 +243,10 @@ const Chat = (props: Props) => { {' '} Cancel - diff --git a/frontend/chat-plugin/src/components/newConversation/index.tsx b/frontend/chat-plugin/src/components/newConversation/index.tsx index 8031cd07d0..52d25883e2 100644 --- a/frontend/chat-plugin/src/components/newConversation/index.tsx +++ b/frontend/chat-plugin/src/components/newConversation/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import style from './index.module.scss'; +import {cyChatPluginStartNewConversation} from 'chat-plugin-handles'; type newConversationProps = { reAuthenticate: () => void; @@ -14,7 +15,11 @@ const NewConversation = (props: newConversationProps) => { diff --git a/integration/chat-plugin/end_conversation.spec.ts b/integration/chat-plugin/end_conversation.spec.ts new file mode 100644 index 0000000000..288ef3c7ef --- /dev/null +++ b/integration/chat-plugin/end_conversation.spec.ts @@ -0,0 +1,31 @@ +import { + cyBubble, + cyInputbarTextarea, + cyInputbarButton, + cyChatPluginMessageList, + cyChatPluginHeaderBarCloseButton, + cyChatPluginEndChatModalButton, + cyChatPluginStartNewConversation, +} from 'chat-plugin-handles'; + +describe('End ChatPlugin Conversation', () => { + it('Send message from Inbox to Chatplugin, ends the current conversation and starts a new conversation', () => { + cy.visit('/chatplugin/ui/example?channel_id=' + Cypress.env('channelId')); + cy.get(`[data-cy=${cyBubble}]`).click(); + cy.get(`[data-cy=${cyInputbarTextarea}]`).type(Cypress.env('messageChatplugin')); + cy.get(`[data-cy=${cyInputbarButton}]`).click(); + cy.get(`[data-cy=${cyChatPluginMessageList}]`).children().should('have.length', 2) + + cy.wait(500); + + cy.get(`[data-cy=${cyChatPluginHeaderBarCloseButton}]`).click(); + cy.get(`[data-cy=${cyChatPluginEndChatModalButton}]`).click(); + cy.get(`[data-cy=${cyChatPluginStartNewConversation}]`).click(); + cy.get(`[data-cy=${cyBubble}]`).click(); + cy.get(`[data-cy=${cyInputbarTextarea}]`).type(Cypress.env('newMessageChatplugin')); + cy.get(`[data-cy=${cyInputbarButton}]`).click(); + cy.wait(500); + + cy.get(`[data-cy=${cyChatPluginMessageList}]`).children().should('have.length', 2) + }); +}); diff --git a/integration/chat-plugin/websocket_test.spec.ts b/integration/chat-plugin/websocket_test.spec.ts index c34e7d5b10..5e70217bab 100644 --- a/integration/chat-plugin/websocket_test.spec.ts +++ b/integration/chat-plugin/websocket_test.spec.ts @@ -43,7 +43,6 @@ describe('Websocket test', () => { password: Cypress.env('password'), }).then(response => { const loginToken = response.body['token']; - console.log('LoginToken', loginToken); cy.request({ method: 'POST', diff --git a/integration/cypress.json b/integration/cypress.json index 011594abd8..5f905238e1 100644 --- a/integration/cypress.json +++ b/integration/cypress.json @@ -10,6 +10,7 @@ "searchQuery": "Cypress Filter", "messageInbox": "Hello from Cypress Inbox!", "messageChatplugin": "Hello from Cypress ChatPlugin!", + "newMessageChatplugin": "Hello once again from Cypress ChatPlugin!", "websocketMessage": "Websocket Test Message", "channelId": "3502a0a7-933d-5410-b5fc-51f041146d89" }, diff --git a/integration/ui/send_chatplugin_message.spec.ts b/integration/ui/send_chatplugin_message.spec.ts index 4d1fdd09c6..02c34488c5 100644 --- a/integration/ui/send_chatplugin_message.spec.ts +++ b/integration/ui/send_chatplugin_message.spec.ts @@ -36,7 +36,7 @@ describe('Send chat plugin message', () => { cy.wait(500); cy.get(`[data-cy=${cyChannelsChatPluginList}]`).filter(`:contains("${Cypress.env('chatPluginName')}")`); - cy.visit('http://airy.core/chatplugin/ui/example?channel_id=' + Cypress.env('channelId')); + cy.visit('/chatplugin/ui/example?channel_id=' + Cypress.env('channelId')); cy.get(`[data-cy=${cyBubble}]`).click(); cy.get(`[data-cy=${cyInputbarTextarea}]`).type(Cypress.env('messageChatplugin')); cy.get(`[data-cy=${cyInputbarButton}]`).click(); From fb49f4d2fa41f77f4a45eaa0e689871cb77bff36 Mon Sep 17 00:00:00 2001 From: AudreyKj <38159391+AudreyKj@users.noreply.github.com> Date: Thu, 15 Apr 2021 13:28:09 +0200 Subject: [PATCH 16/50] fixed template button (#1556) * resized template scg * runned svhgo to reduce svg --- frontend/ui/src/pages/Inbox/MessageInput/index.module.scss | 2 -- lib/typescript/assets/images/icons/template-alt.svg | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/ui/src/pages/Inbox/MessageInput/index.module.scss b/frontend/ui/src/pages/Inbox/MessageInput/index.module.scss index b43a2f45ac..c27c3d3c1d 100644 --- a/frontend/ui/src/pages/Inbox/MessageInput/index.module.scss +++ b/frontend/ui/src/pages/Inbox/MessageInput/index.module.scss @@ -152,8 +152,6 @@ justify-content: center; align-items: center; position: relative; - margin-left: 4px; - width: 30px; height: 30px; svg { diff --git a/lib/typescript/assets/images/icons/template-alt.svg b/lib/typescript/assets/images/icons/template-alt.svg index 214ba0e272..f38cdc71aa 100644 --- a/lib/typescript/assets/images/icons/template-alt.svg +++ b/lib/typescript/assets/images/icons/template-alt.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From e2d1c791e131d937226d14babb4f1f0ca017f019 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Apr 2021 16:27:53 +0200 Subject: [PATCH 17/50] Bump react-markdown from 5.0.3 to 6.0.0 (#1554) Bumps [react-markdown](https://github.com/remarkjs/react-markdown) from 5.0.3 to 6.0.0. - [Release notes](https://github.com/remarkjs/react-markdown/releases) - [Changelog](https://github.com/remarkjs/react-markdown/blob/main/changelog.md) - [Commits](https://github.com/remarkjs/react-markdown/compare/5.0.3...6.0.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 187 ++++++++++++++++++++++++++++----------------------- 2 files changed, 103 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index 9728d4e9f1..a15421b958 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "react-color": "^2.19.3", "react-dom": "16.14.0", "react-facebook-login": "^4.1.1", - "react-markdown": "^5.0.3", + "react-markdown": "^6.0.0", "react-modal": "^3.13.1", "react-redux": "7.2.3", "react-router-dom": "5.2.0", diff --git a/yarn.lock b/yarn.lock index 74e34e1134..8b8eb48031 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1299,6 +1299,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/hast@^2.0.0": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.1.tgz#b16872f2a6144c7025f296fb9636a667ebb79cd9" + integrity sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q== + dependencies: + "@types/unist" "*" + "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" @@ -1339,7 +1346,7 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== -"@types/mdast@^3.0.0", "@types/mdast@^3.0.3": +"@types/mdast@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== @@ -2479,6 +2486,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -2977,15 +2989,6 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom-serializer@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1" - integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - entities "^2.0.0" - dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" @@ -3001,11 +3004,6 @@ domelementtype@^2.0.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== -domelementtype@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" - integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== - domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" @@ -3013,20 +3011,6 @@ domhandler@^2.3.0: dependencies: domelementtype "1" -domhandler@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" - integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA== - dependencies: - domelementtype "^2.0.1" - -domhandler@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e" - integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA== - dependencies: - domelementtype "^2.1.0" - domutils@^1.5.1, domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -3035,15 +3019,6 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -domutils@^2.4.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.0.tgz#42f49cffdabb92ad243278b331fd761c1c2d3039" - integrity sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.0.1" - domhandler "^4.0.0" - dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -4056,16 +4031,6 @@ html-minifier-terser@^5.0.1: relateurl "^0.2.7" terser "^4.6.3" -html-to-react@^1.3.4: - version "1.4.5" - resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.4.5.tgz#59091c11021d1ef315ef738460abb6a4a41fe1ce" - integrity sha512-KONZUDFPg5OodWaQu2ymfkDmU0JA7zB1iPfvyHehTmMUZnk0DS7/TyCMTzsLH6b4BvxX15g88qZCXFhJWktsmA== - dependencies: - domhandler "^3.3.0" - htmlparser2 "^5.0" - lodash.camelcase "^4.3.0" - ramda "^0.27.1" - html-webpack-plugin@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.3.1.tgz#8797327548e3de438e3494e0c6d06f181a7f20d1" @@ -4089,16 +4054,6 @@ htmlparser2@^3.10.1: inherits "^2.0.1" readable-stream "^3.1.1" -htmlparser2@^5.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-5.0.1.tgz#7daa6fc3e35d6107ac95a4fc08781f091664f6e7" - integrity sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ== - dependencies: - domelementtype "^2.0.1" - domhandler "^3.3.0" - domutils "^2.4.2" - entities "^2.0.0" - http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -4268,6 +4223,11 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -4928,11 +4888,6 @@ lodash-es@^4.17.15, lodash-es@^4.17.21: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -5037,12 +4992,12 @@ material-colors@^1.2.1: resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== -mdast-add-list-metadata@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf" - integrity sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA== +mdast-util-definitions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" + integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== dependencies: - unist-util-visit-parents "1.1.2" + unist-util-visit "^2.0.0" mdast-util-from-markdown@^0.8.0: version "0.8.5" @@ -5055,6 +5010,20 @@ mdast-util-from-markdown@^0.8.0: parse-entities "^2.0.0" unist-util-stringify-position "^2.0.0" +mdast-util-to-hast@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz#61875526a017d8857b71abc9333942700b2d3604" + integrity sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + mdast-util-definitions "^4.0.0" + mdurl "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + mdast-util-to-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" @@ -5070,6 +5039,11 @@ mdn-data@2.0.4: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== +mdurl@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -5868,6 +5842,13 @@ prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.6.0, prop-types@^15.6.1, object-assign "^4.1.1" react-is "^16.8.1" +property-information@^5.0.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + protobufjs@6.8.8: version "6.8.8" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" @@ -5958,7 +5939,7 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -ramda@^0.27.1, ramda@~0.27.1: +ramda@~0.27.1: version "0.27.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== @@ -6036,31 +6017,38 @@ react-hot-loader@^4.13.0: shallowequal "^1.1.0" source-map "^0.7.3" -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.0: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-markdown@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-5.0.3.tgz#41040ea7a9324b564b328fb81dd6c04f2a5373ac" - integrity sha512-jDWOc1AvWn0WahpjW6NK64mtx6cwjM4iSsLHJPNBqoAgGOVoIdJMqaKX4++plhOtdd4JksdqzlDibgPx6B/M2w== +react-markdown@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-6.0.0.tgz#e63cd32d095e864384d524986c44c34c919de517" + integrity sha512-MC+zljUJeoLb4RbDm/wRbfoQFEZGz4TDOt/wb4dEehdaJWxLMn/T2IgwhQy0VYhuPEd2fhd7iOayE8lmENU0FA== dependencies: - "@types/mdast" "^3.0.3" + "@types/hast" "^2.0.0" "@types/unist" "^2.0.3" - html-to-react "^1.3.4" - mdast-add-list-metadata "1.0.1" + comma-separated-tokens "^1.0.0" prop-types "^15.7.2" - react-is "^16.8.6" + property-information "^5.0.0" + react-is "^17.0.0" remark-parse "^9.0.0" + remark-rehype "^8.0.0" + space-separated-tokens "^1.1.0" + style-to-object "^0.3.0" unified "^9.0.0" unist-util-visit "^2.0.0" - xtend "^4.0.1" react-modal@^3.13.1: version "3.13.1" @@ -6281,6 +6269,13 @@ remark-parse@^9.0.0: dependencies: mdast-util-from-markdown "^0.8.0" +remark-rehype@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-8.1.0.tgz#610509a043484c1e697437fa5eb3fd992617c945" + integrity sha512-EbCu9kHgAxKmW1yEYjx3QafMyGY3q8noUbNUI5xyKbaFP89wbhDrKxyIQNukNYthzjNHZu6J7hwFg7hRm1svYA== + dependencies: + mdast-util-to-hast "^10.2.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -6824,6 +6819,11 @@ source-map@^0.7.3, source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +space-separated-tokens@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -7021,6 +7021,13 @@ style-loader@^2.0.0: loader-utils "^2.0.0" schema-utils "^3.0.0" +style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -7361,11 +7368,26 @@ uniq@^1.0.1: resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= +unist-builder@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" + integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== + +unist-util-generated@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" + integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== + unist-util-is@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== +unist-util-position@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" + integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== + unist-util-stringify-position@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" @@ -7373,11 +7395,6 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" -unist-util-visit-parents@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06" - integrity sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q== - unist-util-visit-parents@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" @@ -7790,7 +7807,7 @@ ws@^7.3.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== -xtend@^4.0.1: +xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From 6c2b0f75693a4b27edc470fa86299ca2f3c95aa5 Mon Sep 17 00:00:00 2001 From: Paulo Diniz Date: Thu, 15 Apr 2021 16:52:34 +0200 Subject: [PATCH 18/50] [#740] Uses components endpoint on service discovery (#1549) * [#740] Uses components endpoint on service discovery --- .../api/config/ClientConfigController.java | 3 -- .../config/ClientConfigResponsePayload.java | 1 - .../api/config/ComponentResponsePayload.java | 17 ++++++++++ .../api/config/ComponentsResponsePayload.java | 17 ++++++++++ .../core/api/config/ServiceDiscovery.java | 27 ++++++---------- .../config/ClientConfigControllerTest.java | 28 ++++++---------- .../ui/src/pages/Channels/MainPage/index.tsx | 2 +- frontend/ui/src/reducers/data/config/index.ts | 32 ++----------------- lib/typescript/model/Config.ts | 16 +--------- 9 files changed, 58 insertions(+), 85 deletions(-) create mode 100644 backend/api/admin/src/main/java/co/airy/core/api/config/ComponentResponsePayload.java create mode 100644 backend/api/admin/src/main/java/co/airy/core/api/config/ComponentsResponsePayload.java diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java b/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java index 1bd872f298..5c4d35c666 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigController.java @@ -4,8 +4,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.Map; - @RestController public class ClientConfigController { private final ServiceDiscovery serviceDiscovery; @@ -18,7 +16,6 @@ public ClientConfigController(ServiceDiscovery serviceDiscovery) { public ResponseEntity getConfig() { return ResponseEntity.ok(ClientConfigResponsePayload.builder() .components(serviceDiscovery.getComponents()) - .features(Map.of()) .build()); } } diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigResponsePayload.java b/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigResponsePayload.java index bd11534e48..56f96a52b6 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigResponsePayload.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/config/ClientConfigResponsePayload.java @@ -13,5 +13,4 @@ @AllArgsConstructor public class ClientConfigResponsePayload { private Map> components; - private Map features; } diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ComponentResponsePayload.java b/backend/api/admin/src/main/java/co/airy/core/api/config/ComponentResponsePayload.java new file mode 100644 index 0000000000..19a56f14c0 --- /dev/null +++ b/backend/api/admin/src/main/java/co/airy/core/api/config/ComponentResponsePayload.java @@ -0,0 +1,17 @@ +package co.airy.core.api.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class ComponentResponsePayload { + private String name; + private Boolean enabled; +} diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ComponentsResponsePayload.java b/backend/api/admin/src/main/java/co/airy/core/api/config/ComponentsResponsePayload.java new file mode 100644 index 0000000000..34e69af59d --- /dev/null +++ b/backend/api/admin/src/main/java/co/airy/core/api/config/ComponentsResponsePayload.java @@ -0,0 +1,17 @@ +package co.airy.core.api.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ComponentsResponsePayload implements Serializable { + private List components; +} diff --git a/backend/api/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java b/backend/api/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java index 90e5ef6f0f..c369dd7fff 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/config/ServiceDiscovery.java @@ -1,29 +1,23 @@ package co.airy.core.api.config; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; -import java.util.List; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; @Component public class ServiceDiscovery { private final String namespace; private final RestTemplate restTemplate; - private final Map> components = new ConcurrentHashMap<>(); - - private static final List services = List.of( - "sources-chatplugin", - "sources-facebook-connector", - "sources-twilio-connector", - "sources-google-connector" - ); + private Map> components = new ConcurrentHashMap<>(); public ServiceDiscovery(@Value("${kubernetes.namespace}") String namespace, RestTemplate restTemplate) { this.namespace = namespace; @@ -36,13 +30,12 @@ public Map> getComponents() { @Scheduled(fixedRate = 1_000) private void updateComponentsStatus() { - for (String service : services) { - try { - ResponseEntity response = restTemplate.exchange(String.format("http://%s.%s/actuator/health", service, namespace), HttpMethod.GET, null, Object.class); - components.put(service.replace("-connector", ""), Map.of("enabled", response.getStatusCode().is2xxSuccessful())); - } catch (Exception e) { - components.put(service.replace("-connector", ""), Map.of("enabled",false)); - } + final ResponseEntity response = restTemplate.getForEntity("http://airy-controller.default/components", ComponentsResponsePayload.class); + Map> newComponents = new ConcurrentHashMap<>(); + for (String component: response.getBody().getComponents()) { + newComponents.put(component, Map.of("enabled", true)); } + components.clear(); + components.putAll(newComponents); } } diff --git a/backend/api/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java b/backend/api/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java index e6d0654942..d8437bb616 100644 --- a/backend/api/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java +++ b/backend/api/admin/src/test/java/co/airy/core/api/config/ClientConfigControllerTest.java @@ -20,7 +20,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.client.MockRestServiceServer; @@ -31,11 +31,12 @@ import static co.airy.test.Timing.retryOnException; import static org.hamcrest.CoreMatchers.everyItem; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import static org.springframework.test.web.client.ExpectedCount.min; +import static org.springframework.test.web.client.ExpectedCount.once; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -91,25 +92,16 @@ void beforeEach() throws Exception { @Test public void canReturnConfig() throws Exception { - mockServer.expect(min(1), requestTo(new URI("http://sources-chatplugin.default/actuator/health"))) + mockServer.expect(once(), requestTo(new URI("http://airy-controller.default/components"))) .andExpect(method(HttpMethod.GET)) - .andRespond(withStatus(HttpStatus.OK)); - - mockServer.expect(min(1), requestTo(new URI("http://sources-facebook-connector.default/actuator/health"))) - .andExpect(method(HttpMethod.GET)) - .andRespond(withStatus(HttpStatus.OK)); - - mockServer.expect(min(1), requestTo(new URI("http://sources-twilio-connector.default/actuator/health"))) - .andExpect(method(HttpMethod.GET)) - .andRespond(withStatus(HttpStatus.OK)); - - mockServer.expect(min(1), requestTo(new URI("http://sources-google-connector.default/actuator/health"))) - .andExpect(method(HttpMethod.GET)) - .andRespond(withStatus(HttpStatus.OK)); + .andRespond( + withSuccess("{\"components\": [\"api-communication\"]}", MediaType.APPLICATION_JSON) + ); retryOnException(() -> webTestHelper.post("/client.config", "{}", "user-id") .andExpect(status().isOk()) - .andExpect(jsonPath("$.components.*", hasSize(4))) + .andExpect(jsonPath("$.components.*", hasSize(1))) + .andExpect(jsonPath("$.components", hasKey("api-communication"))) .andExpect(jsonPath("$.components.*.enabled", everyItem(is(true)))), "client.config call failed"); } diff --git a/frontend/ui/src/pages/Channels/MainPage/index.tsx b/frontend/ui/src/pages/Channels/MainPage/index.tsx index be9710681d..94b30c8c6f 100644 --- a/frontend/ui/src/pages/Channels/MainPage/index.tsx +++ b/frontend/ui/src/pages/Channels/MainPage/index.tsx @@ -163,7 +163,7 @@ const MainPage = (props: MainPageProps & RouteComponentProps) => { sourceInfo={infoItem} displayButton={!channelsBySource(infoItem.type).length} addChannelAction={() => { - if (config.components[infoItem.configKey].enabled) { + if (config.components[infoItem.configKey] && config.components[infoItem.configKey].enabled) { props.history.push(infoItem.newChannelRoute); } else { setDisplayDialogFromSource(infoItem.type); diff --git a/frontend/ui/src/reducers/data/config/index.ts b/frontend/ui/src/reducers/data/config/index.ts index ccbf384411..4dde971168 100644 --- a/frontend/ui/src/reducers/data/config/index.ts +++ b/frontend/ui/src/reducers/data/config/index.ts @@ -4,39 +4,11 @@ import * as actions from '../../../actions/config'; type Action = ActionType; export type Config = { - components: { - 'sources-chatplugin': { - enabled: boolean; - }; - 'sources-facebook': { - enabled: boolean; - }; - 'sources-google': { - enabled: boolean; - }; - 'sources-twilio': { - enabled: boolean; - }; - }; - features: {}; + components: {[key: string]: {enabled: boolean}}; }; const defaultState = { - components: { - 'sources-chatplugin': { - enabled: false, - }, - 'sources-facebook': { - enabled: false, - }, - 'sources-google': { - enabled: false, - }, - 'sources-twilio': { - enabled: false, - }, - }, - features: {}, + components: {}, }; export default function configReducer(state = defaultState, action: Action): Config { diff --git a/lib/typescript/model/Config.ts b/lib/typescript/model/Config.ts index 9a22581132..bbf7e380b8 100644 --- a/lib/typescript/model/Config.ts +++ b/lib/typescript/model/Config.ts @@ -1,17 +1,3 @@ export interface Config { - components: { - 'sources-chatplugin': { - enabled: boolean; - }; - 'sources-facebook': { - enabled: boolean; - }; - 'sources-google': { - enabled: boolean; - }; - 'sources-twilio': { - enabled: boolean; - }; - }; - features: {}; + components: {[key: string]: {enabled: boolean}}; } From ce6aac9b7df54d52758dad2da65f2e8b64b26415 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Apr 2021 16:54:56 +0200 Subject: [PATCH 19/50] Bump @types/node from 14.14.39 to 14.14.40 (#1559) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.39 to 14.14.40. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a15421b958..2c2ee8f596 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@crello/react-lottie": "^0.0.11", "@reduxjs/toolkit": "^1.5.1", "@stomp/stompjs": "^6.1.0", - "@types/node": "14.14.39", + "@types/node": "14.14.40", "@types/react": "16.9.34", "@types/react-dom": "16.9.2", "@types/react-redux": "7.1.16", diff --git a/yarn.lock b/yarn.lock index 8b8eb48031..a3cf751ae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1358,10 +1358,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== -"@types/node@*", "@types/node@14.14.39", "@types/node@^14.14.31": - version "14.14.39" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.39.tgz#9ef394d4eb52953d2890e4839393c309aa25d2d1" - integrity sha512-Qipn7rfTxGEDqZiezH+wxqWYR8vcXq5LRpZrETD19Gs4o8LbklbmqotSUsMU+s5G3PJwMRDfNEYoxrcBwIxOuw== +"@types/node@*", "@types/node@14.14.40", "@types/node@^14.14.31": + version "14.14.40" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.40.tgz#05a7cd31154487f357ca0bec4334ed1b1ab825a0" + integrity sha512-2HoZZGylcnz19ZSbvWhgWHqvprw1ZGHanxIrDWYykPD4CauLW4gcyLzCVfUN2kv/1t1F3CurQIdi+s1l9+XgEA== "@types/node@^10.1.0": version "10.17.55" From 244aadb528f1bdd26fb32753de780dca7315695a Mon Sep 17 00:00:00 2001 From: Christoph Proeschel Date: Thu, 15 Apr 2021 17:26:44 +0200 Subject: [PATCH 20/50] [#1399] Add Rasa suggested reply guide (#1548) --- .../core/api/admin/ChannelsController.java | 2 +- .../airy/model/metadata/dto/MetadataNode.java | 2 + docs/docs/api/endpoints/chatplugin.md | 9 +- docs/docs/api/endpoints/conversations.md | 6 +- .../api/endpoints/google-messages-send.mdx | 30 +++--- docs/docs/api/endpoints/messages-send.mdx | 6 +- docs/docs/api/endpoints/messages.md | 3 +- docs/docs/api/endpoints/suggest-replies.mdx | 36 +++---- docs/docs/api/event-payloads.mdx | 3 +- docs/docs/api/webhook.md | 3 +- docs/docs/api/websocket.md | 3 +- docs/docs/getting-started/components.md | 2 +- docs/docs/guides/operations.md | 2 +- .../{rasa.md => rasa-assistant.md} | 15 ++- .../integrations/rasa-suggested-replies.md | 100 ++++++++++++++++++ docs/docs/ui/suggestedReplies.md | 2 +- docs/sidebars.js | 2 +- .../integrations/rasa/suggested-replies.gif | Bin 0 -> 250680 bytes 18 files changed, 159 insertions(+), 67 deletions(-) rename docs/docs/integrations/{rasa.md => rasa-assistant.md} (91%) create mode 100644 docs/docs/integrations/rasa-suggested-replies.md create mode 100644 docs/static/img/integrations/rasa/suggested-replies.gif diff --git a/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java b/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java index 1ea8e99108..5946befbee 100644 --- a/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java +++ b/backend/api/admin/src/main/java/co/airy/core/api/admin/ChannelsController.java @@ -86,7 +86,7 @@ ResponseEntity connect(@RequestBody @Valid ConnectChannelRequestPayload reque final String sourceChannelId = requestPayload.getName(); final String sourceIdentifier = "chatplugin"; - final String channelId = UUIDv5.fromNamespaceAndName(sourceIdentifier, sourceChannelId).toString(); + final String channelId = UUID.randomUUID().toString(); List metadataList = new ArrayList<>(); metadataList.add(newChannelMetadata(channelId, MetadataKeys.ChannelKeys.NAME, requestPayload.getName())); diff --git a/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataNode.java b/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataNode.java index 483f272a5a..4c6957243d 100644 --- a/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataNode.java +++ b/backend/model/metadata/src/main/java/co/airy/model/metadata/dto/MetadataNode.java @@ -2,11 +2,13 @@ import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; @Data @AllArgsConstructor +@NoArgsConstructor public class MetadataNode implements Serializable { private String key; private String value; diff --git a/docs/docs/api/endpoints/chatplugin.md b/docs/docs/api/endpoints/chatplugin.md index 29d04e1433..ecefde4b54 100644 --- a/docs/docs/api/endpoints/chatplugin.md +++ b/docs/docs/api/endpoints/chatplugin.md @@ -48,8 +48,7 @@ previous conversation using the [resume endpoint](#get-a-resume-token). // source message payload "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "metadata": { @@ -114,8 +113,7 @@ header. // source message payload "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "metadata": { @@ -147,8 +145,7 @@ The WebSocket connection endpoint is at `/ws.chatplugin`. // source message payload "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "metadata": { diff --git a/docs/docs/api/endpoints/conversations.md b/docs/docs/api/endpoints/conversations.md index 03ff2fe231..38cc792476 100644 --- a/docs/docs/api/endpoints/conversations.md +++ b/docs/docs/api/endpoints/conversations.md @@ -72,8 +72,7 @@ Find users whose name ends with "Lovelace": // typed source message model state: "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - sender_type: "{string/enum}", - // See glossary + "from_contact": true, sent_at: "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "source": "{String}" @@ -133,8 +132,7 @@ Find users whose name ends with "Lovelace": // typed source message model "delivery_state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}" //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients } diff --git a/docs/docs/api/endpoints/google-messages-send.mdx b/docs/docs/api/endpoints/google-messages-send.mdx index f398b07c58..cd306095dd 100644 --- a/docs/docs/api/endpoints/google-messages-send.mdx +++ b/docs/docs/api/endpoints/google-messages-send.mdx @@ -11,13 +11,13 @@ Whatever is put on the `message` field will be forwarded "as-is" to the source's ```json5 { - conversation_id: 'a688d36c-a85e-44af-bc02-4248c2c97622', - message: { - text: 'Hello World Agent!', - representative: { - displayName: 'Hello World from Agent', - avatarImage: 'REPRESENTATIVE_AVATAR_URL', - representativeType: 'HUMAN', + "conversation_id": "a688d36c-a85e-44af-bc02-4248c2c97622", + "message": { + "text": "Hello World Agent!", + "representative": { + "displayName": "Hello World from Agent", + "avatarImage": "REPRESENTATIVE_AVATAR_URL", + "representativeType": "HUMAN", }, }, } @@ -27,17 +27,17 @@ Whatever is put on the `message` field will be forwarded "as-is" to the source's ```json5 { - id: '{UUID}', - content: '{"text":"Hello"}', - state: 'pending|failed|delivered', - sender_type: '{string/enum}', + "id": "{UUID}", + "content": "{\"text\":\"Hello\"}", + "state": "pending|failed|delivered", + "from_contact": true, // See glossary - sent_at: '{string}', + "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients - source: '{String}', + "source": "{String}", // one of the possible sources - metadata: { - sentFrom: 'iPhone', + "metadata": { + "sentFrom": "iPhone", }, // metadata object of the message } diff --git a/docs/docs/api/endpoints/messages-send.mdx b/docs/docs/api/endpoints/messages-send.mdx index 8caa442c4f..f3c23bfc61 100644 --- a/docs/docs/api/endpoints/messages-send.mdx +++ b/docs/docs/api/endpoints/messages-send.mdx @@ -19,9 +19,9 @@ Sends a message to a conversation and returns a payload. Whatever is put on the ```json5 { "id": "{UUID}", - "content": '{"text":"Hello"}', + "content": "{\"text\":\"Hello\"}", "state": "pending|failed|delivered", - "sender_type": "{string/enum}", + "from_contact": true, // See glossary "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients @@ -32,4 +32,4 @@ Sends a message to a conversation and returns a payload. Whatever is put on the } // metadata object of the message } -``` \ No newline at end of file +``` diff --git a/docs/docs/api/endpoints/messages.md b/docs/docs/api/endpoints/messages.md index 81547029b3..340adc2eca 100644 --- a/docs/docs/api/endpoints/messages.md +++ b/docs/docs/api/endpoints/messages.md @@ -34,8 +34,7 @@ latest. // source message payload "state": "{String}", // delivery state of message, one of PENDING, FAILED, DELIVERED - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "source": "{String}", diff --git a/docs/docs/api/endpoints/suggest-replies.mdx b/docs/docs/api/endpoints/suggest-replies.mdx index 86b7c48cd1..4bd3554193 100644 --- a/docs/docs/api/endpoints/suggest-replies.mdx +++ b/docs/docs/api/endpoints/suggest-replies.mdx @@ -6,13 +6,13 @@ Suggest a set of replies for a given message id. UI clients can then show these ```json5 { - message_id: 'uuid', - suggestions: { - 'suggestion-id-1': { - content: {text: 'Great that it worked. Is there anything else you need?'}, + "message_id": "uuid", + "suggestions": { + "suggestion-id-1": { + "content": {"text": "Great that it worked. Is there anything else you need?"}, }, - 'suggestion-id-2': { - content: {text: 'Have a nice day!'}, + "suggestion-id-2": { + "content": {"text": "Have a nice day!"}, }, }, } @@ -24,20 +24,20 @@ The updated message including the suggested replies. ```json5 { - id: '{UUID}', - content: {text: 'Hello World'}, - state: '{String}', - sender_type: '{string/enum}', - sent_at: '{string}', - source: '{String}', - metadata: { - suggestions: { - 'suggestion-id-1': { - content: {text: 'Great that it worked. Is there anything else you need?'}, + "id": "{UUID}", + "content": {"text": "Hello World"}, + "state": "{String}", + "from_contact": true, + "sent_at": "{string}", + "source": "{String}", + "metadata": { + "suggestions": { + "suggestion-id-1": { + "content": {"text": "Great that it worked. Is there anything else you need?"}, // source specific content field (same as message content) }, - 'suggestion-id-2': { - content: {text: 'Have a nice day!'}, + "suggestion-id-2": { + "content": {"text": "Have a nice day!"}, }, }, }, diff --git a/docs/docs/api/event-payloads.mdx b/docs/docs/api/event-payloads.mdx index d7faed2e45..b0a7abc7ea 100644 --- a/docs/docs/api/event-payloads.mdx +++ b/docs/docs/api/event-payloads.mdx @@ -12,8 +12,7 @@ // source message payload "delivery_state": "pending|failed|delivered", // delivery state of message, one of pending, failed, delivered - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "source": "{String}", diff --git a/docs/docs/api/webhook.md b/docs/docs/api/webhook.md index 9a7a9b2b0e..af57b46321 100644 --- a/docs/docs/api/webhook.md +++ b/docs/docs/api/webhook.md @@ -106,8 +106,7 @@ request with one the following payloads: // source message payload "delivery_state": "pending|failed|delivered", // delivery state of message, one of pending, failed, delivered - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "source": "{String}" diff --git a/docs/docs/api/websocket.md b/docs/docs/api/websocket.md index 006a661579..0af79384bd 100644 --- a/docs/docs/api/websocket.md +++ b/docs/docs/api/websocket.md @@ -39,8 +39,7 @@ field informs the client of the kind of update that is encoded in the payload. // source message payload "delivery_state": "pending|failed|delivered", // delivery state of message, one of pending, failed, delivered - "sender_type": "{string/enum}", - // See glossary + "from_contact": true, "sent_at": "{string}", //'yyyy-MM-dd'T'HH:mm:ss.SSSZ' date in UTC form, to be localized by clients "source": "{String}" diff --git a/docs/docs/getting-started/components.md b/docs/docs/getting-started/components.md index c675a38df1..1cd60eeacc 100644 --- a/docs/docs/getting-started/components.md +++ b/docs/docs/getting-started/components.md @@ -65,6 +65,6 @@ Airy Core contains the following core components: iconInvertible={true} title='Integrations' description="Pre-made integrations into popular conversational tools, for example NLP tools like Rasa" - link='/integrations/rasa' + link='/integrations/rasa-assistant' /> diff --git a/docs/docs/guides/operations.md b/docs/docs/guides/operations.md index 8ad07d2262..b3ef55fb38 100644 --- a/docs/docs/guides/operations.md +++ b/docs/docs/guides/operations.md @@ -55,7 +55,7 @@ be used for all sorts of dashboard and monitoring requirements. `k edit cm prometheus-grafana` -```yaml +```toml [server] domain = root_url = /grafana diff --git a/docs/docs/integrations/rasa.md b/docs/docs/integrations/rasa-assistant.md similarity index 91% rename from docs/docs/integrations/rasa.md rename to docs/docs/integrations/rasa-assistant.md index bcdfbf423e..0bb94a9c0d 100644 --- a/docs/docs/integrations/rasa.md +++ b/docs/docs/integrations/rasa-assistant.md @@ -1,6 +1,6 @@ --- -title: Rasa integration -sidebar_label: Rasa +title: Rasa Chat Assistant +sidebar_label: Rasa Assistant --- import useBaseUrl from '@docusaurus/useBaseUrl'; @@ -32,14 +32,14 @@ array of messaging channels and service them in a single inbox. For Rasa, you can think of it as a forward messaging router that will persist your data and make it available for export to anywhere within your organization. -This guide covers how to configure your Rasa installation so that it can use the +This guide covers how to configure your Rasa installation so that it can use Airy Core to send and receive messages. :::note Prerequisites - A running Airy Core installation [Get Started](getting-started/installation/introduction.md) -- A local Rasa setup: For convenience, we recommend [the Docker setup](https://rasa.com/docs/rasa/docker/building-in-docker/) or [a demo repository](https://github.com/airyhq/rasa-demo) we created for this guide +- A local Rasa setup: For convenience, we recommend [the Docker setup](https://rasa.com/docs/rasa/docker/building-in-docker/) or [the demo repository](https://github.com/airyhq/rasa-demo) we created for this guide ::: @@ -84,16 +84,15 @@ copy this [connector file](https://github.com/airyhq/rasa-demo/blob/master/channels/airy.py) into it. The connector requires the following configuration values: -- `auth_token` the Airy Core JWT token you used - to connect the webhook. +- `system_token` the Airy Core system token used for authenticating with the API. - `api_host` The url where you host your Airy Core API. Add them to your existing Rasa `credentials.yml`: ```yaml channels.airy.AiryInput: - api_host: "your JWT authentication token" - auth_token: "http://airy.core" + api_host: "http://airy.core" + system_token: "the system api token for Airy Core" ``` Now you should have a working integration πŸŽ‰. diff --git a/docs/docs/integrations/rasa-suggested-replies.md b/docs/docs/integrations/rasa-suggested-replies.md new file mode 100644 index 0000000000..4915f4a8b9 --- /dev/null +++ b/docs/docs/integrations/rasa-suggested-replies.md @@ -0,0 +1,100 @@ +--- +title: Suggested replies with Rasa +sidebar_label: Rasa Suggested Replies +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +:::note Prerequisites + +This guide assumes that you completed the [Rasa Chat assistant guide](/integrations/rasa-assistant.md), which means you have: + +- a running Airy Core instance +- a Rasa setup connected to that instance with a custom channel (see the [demo repository](https://github.com/airyhq/rasa-demo)) + +::: + +## How it works + +see suggested replies in the Airy inbox when receiving a contact greeting + +Chatbots can serve a wide variety of use cases like answering frequently asked questions or booking flows. +Customer support however often requires a human agent to serve user questions with a high degree of quality. With Airy +Core you can get the best of both worlds by using NLP frameworks like Rasa to suggest a set of replies to the agent. +This way agents can handle the vast majority of use cases with the click of a button (see screenshot). + +## Configuring Rasa + +- [Step 1: Add a custom response type](#step-1-add-a-custom-response-type) +- [Step 2: Update the user stories](#step-2-update-the-user-stories) +- [Step 3: Extend the Airy connector](#step-3-extend-the-airy-connector) +- [Step 4: Retrain and restart](#step-4-consume-directly-from-apache-kafka) + +### Step 1: Add a custom response type + +The easiest way to instruct Rasa to suggest replies for a given user messages is by adding them as a [custom response type](https://rasa.com/docs/rasa/responses/#custom-output-payloads). To do this we add the following block to the `responses` section in our `domain.yaml`: + +```yaml +responses: + utter_suggest_greet: + - custom: + suggest-informal: + content: + text: "Hey, what's up?" + suggest-formal: + content: + text: "Hi, what can I help you with?" +``` + +### Step 2: Update the user stories + +Now we can use this new response type in our `stories.yaml` to let the bot know when to suggest replies: + +```yaml +stories: + - story: happy path + steps: + - intent: greet + - action: utter_suggest_greet + - intent: mood_great + - action: utter_happy +``` + +### Step 3: Extend the Airy connector + +Now we need to update our [custom Rasa connector](https://rasa.com/docs/rasa/connectors/custom-connectors/) for Airy Core to this response type. For +this we extend the [send_response method](https://github.com/airyhq/rasa-demo/blob/4f2fdd6063385cea805f2d70755733de347e8792/channels/airy.py#L32) in the Airy connector so that it calls the [suggest replies API](/api/endpoints/messages#suggested-replies) whenever +it encounters a custom response payload: + +```python +async def send_response(self, recipient_id: Text, message: Dict[Text, Any]) -> None: + headers = { + "Authorization": self.system_token + } + if message.get("custom"): + body = { + "message_id": self.last_message_id, + "suggestions": message.get("custom") + } + requests.post("{}/messages.suggestReplies".format(self.api_host), headers=headers, json=body) + elif message.get("text"): + body = { + "conversation_id": recipient_id, + "message": { + "text": message.get("text") + } + } + requests.post("{}/messages.send".format(self.api_host), headers=headers, json=body) +``` + +### Step 4: Retrain and restart + +Now we need to stop the server and retrain the model: + +```shell script +rasa train +``` + +Finally, we start the Rasa server, open the Airy Inbox at (`http://airy.core` for local deployments), where we should +see the suggested replies whenever a contact greets us (see gif above). diff --git a/docs/docs/ui/suggestedReplies.md b/docs/docs/ui/suggestedReplies.md index 7656f1fc5d..5f3a6c0058 100644 --- a/docs/docs/ui/suggestedReplies.md +++ b/docs/docs/ui/suggestedReplies.md @@ -42,5 +42,5 @@ You can integrate the Open Source machine learning framework Rasa to automate se icon={} title='Rasa integration' description='Configure Rasa to receive and reply to messages using Airy' -link='integrations/rasa' +link='integrations/rasa-assistant' /> diff --git a/docs/sidebars.js b/docs/sidebars.js index 94a6909d83..f285de2943 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -66,7 +66,7 @@ module.exports = { { 'πŸ› οΈ Integrations': [ { - 'Conversational AI /NLP': ['integrations/rasa'], + 'Conversational AI /NLP': ['integrations/rasa-assistant', 'integrations/rasa-suggested-replies'], }, ], }, diff --git a/docs/static/img/integrations/rasa/suggested-replies.gif b/docs/static/img/integrations/rasa/suggested-replies.gif new file mode 100644 index 0000000000000000000000000000000000000000..b54d1fe453fc70319c9a486a0ec7f5d050450fae GIT binary patch literal 250680 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⪙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

fUNicJt~FKy^UiPS1;{RC>=JN)~p%in|OEC5A9 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

sAld?f~E7 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 literal 0 HcmV?d00001 From 531829c74da5c8972baa903f63cf1a3791bdfd61 Mon Sep 17 00:00:00 2001 From: AudreyKj <38159391+AudreyKj@users.noreply.github.com> Date: Thu, 15 Apr 2021 17:31:48 +0200 Subject: [PATCH 21/50] Fix/635 deployment of the library to npm (#1411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * reintroduces bazel npm deploy rules and edited build file of library * edited build file * fixed build file * fixed naming * fixed path error * fixed bazel label * fixing assemble * lint fix * fixed naming * fixing genrule * saving assemble * fixing output file * fixing ts file errors * save package.json * build file fix * build file fix * genrule fix * fixing genrule * build file fix * added version file * added ci for deploy * removing version file * fixed gen rule and created ssrc folder in the lib * fixed src typo and fixed version file * edited build file * fixed rebased * fixed build file * fixed package.json * Add module deps to web library rule * linting * untracking lock file Co-authored-by: Christoph PrΓΆschel --- .github/workflows/main.yml | 9 ++ BUILD | 1 + lib/typescript/httpclient/BUILD | 57 +++++++- lib/typescript/httpclient/client.ts | 4 +- lib/typescript/httpclient/index.ts | 4 +- lib/typescript/httpclient/package.json | 25 ++++ .../endpoints/connectChatPluginChannel.ts | 0 .../endpoints/connectFacebookChannel.ts | 0 .../endpoints/connectTwilioSmsChannel.ts | 0 .../endpoints/connectTwilioWhatsappChannel.ts | 0 .../{ => src}/endpoints/createTag.ts | 0 .../{ => src}/endpoints/deleteTag.ts | 0 .../{ => src}/endpoints/disconnectChannel.ts | 0 .../endpoints/exploreFacebookChannels.ts | 0 .../{ => src}/endpoints/getConfig.ts | 0 .../endpoints/getConversationInfo.ts | 0 .../httpclient/{ => src}/endpoints/index.ts | 0 .../{ => src}/endpoints/listChannels.ts | 0 .../{ => src}/endpoints/listConversations.ts | 0 .../{ => src}/endpoints/listMessages.ts | 0 .../{ => src}/endpoints/listTags.ts | 0 .../{ => src}/endpoints/listTemplates.ts | 0 .../{ => src}/endpoints/loginViaEmail.ts | 0 .../{ => src}/endpoints/readConversations.ts | 0 .../{ => src}/endpoints/sendMessages.ts | 0 .../{ => src}/endpoints/tagConversation.ts | 0 .../{ => src}/endpoints/untagConversation.ts | 0 .../{ => src}/endpoints/updateChannel.ts | 0 .../{ => src}/endpoints/updateTag.ts | 0 lib/typescript/httpclient/src/index.ts | 3 + .../{ => src}/messagesForChannels/index.ts | 0 .../messagesForChannels/text/index.ts | 0 .../ConnectChannelFacebookRequestPayload.ts | 0 .../payload/ConnectChannelRequestPayload.ts | 0 .../ConnectChatPluginRequestPayload.ts | 0 .../payload/ConnectTwilioSmsRequestPayload.ts | 0 .../ConnectTwilioWhatsappRequestPayload.ts | 0 .../payload/CreateTagRequestPayload.ts | 0 .../DisconnectChannelRequestPayload.ts | 0 .../payload/ExploreChannelRequestPayload.ts | 0 .../ListConversationsRequestPayload.ts | 0 .../payload/ListMessagesRequestPayload.ts | 0 .../payload/ListTemplatesRequestPayload.ts | 0 .../payload/LoginViaEmailRequestPayload.ts | 0 .../{ => src}/payload/PaginatedPayload.ts | 0 .../{ => src}/payload/PaginatedResponse.ts | 0 .../payload/SendMessagesRequestPayload.ts | 0 .../payload/TagConversationRequestPayload.ts | 0 .../UntagConversationRequestPayload.ts | 0 .../payload/UpdateChannelRequestPayload.ts | 0 .../httpclient/{ => src}/payload/index.ts | 0 tools/build/npm/BUILD | 34 +++++ tools/build/npm/README.md | 3 + tools/build/npm/assemble.py | 85 +++++++++++ tools/build/npm/rules.bzl | 136 ++++++++++++++++++ tools/build/npm/templates/BUILD | 20 +++ tools/build/npm/templates/deploy.py | 107 ++++++++++++++ 57 files changed, 480 insertions(+), 8 deletions(-) create mode 100644 lib/typescript/httpclient/package.json rename lib/typescript/httpclient/{ => src}/endpoints/connectChatPluginChannel.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/connectFacebookChannel.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/connectTwilioSmsChannel.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/connectTwilioWhatsappChannel.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/createTag.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/deleteTag.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/disconnectChannel.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/exploreFacebookChannels.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/getConfig.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/getConversationInfo.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/index.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/listChannels.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/listConversations.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/listMessages.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/listTags.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/listTemplates.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/loginViaEmail.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/readConversations.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/sendMessages.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/tagConversation.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/untagConversation.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/updateChannel.ts (100%) rename lib/typescript/httpclient/{ => src}/endpoints/updateTag.ts (100%) create mode 100644 lib/typescript/httpclient/src/index.ts rename lib/typescript/httpclient/{ => src}/messagesForChannels/index.ts (100%) rename lib/typescript/httpclient/{ => src}/messagesForChannels/text/index.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ConnectChannelFacebookRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ConnectChannelRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ConnectChatPluginRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ConnectTwilioSmsRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ConnectTwilioWhatsappRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/CreateTagRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/DisconnectChannelRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ExploreChannelRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ListConversationsRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ListMessagesRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/ListTemplatesRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/LoginViaEmailRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/PaginatedPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/PaginatedResponse.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/SendMessagesRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/TagConversationRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/UntagConversationRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/UpdateChannelRequestPayload.ts (100%) rename lib/typescript/httpclient/{ => src}/payload/index.ts (100%) create mode 100644 tools/build/npm/BUILD create mode 100644 tools/build/npm/README.md create mode 100644 tools/build/npm/assemble.py create mode 100644 tools/build/npm/rules.bzl create mode 100644 tools/build/npm/templates/BUILD create mode 100644 tools/build/npm/templates/deploy.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 23676203f0..ec8c46854d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,4 +61,13 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: Publish http-client library to npm + if: ${{ startsWith(github.ref, 'refs/tags/npm-v') }} + run: | + sudo apt-get install -y expect + bazel run //lib/typescript/httpclient:publish-npm release + env: + DEPLOY_NPM_USERNAME: ${{ secrets.DEPLOY_NPM_USERNAME }} + DEPLOY_NPM_PASSWORD: ${{ secrets.DEPLOY_NPM_PASSWORD }} + DEPLOY_NPM_EMAIL: ${{ secrets.DEPLOY_NPM_EMAIL }} GITHUB_BRANCH: ${{ github.ref }} diff --git a/BUILD b/BUILD index ad2a647e9d..7ee9f23277 100644 --- a/BUILD +++ b/BUILD @@ -180,6 +180,7 @@ exports_files( ".prettierrc.json", ".prettierignore", "yarn.lock", + "VERSION", ], ) diff --git a/lib/typescript/httpclient/BUILD b/lib/typescript/httpclient/BUILD index 5d12a453f0..2558c8b93e 100644 --- a/lib/typescript/httpclient/BUILD +++ b/lib/typescript/httpclient/BUILD @@ -1,19 +1,70 @@ load("//tools/lint:web.bzl", "web_lint") load("@com_github_airyhq_bazel_tools//lint:buildifier.bzl", "check_pkg") load("@com_github_airyhq_bazel_tools//web:typescript.bzl", "ts_web_library") +load("//tools/build/npm:rules.bzl", "assemble_npm", "deploy_npm") +load("@com_github_airyhq_bazel_tools//web:web_library.bzl", "web_library") package(default_visibility = ["//visibility:public"]) +module_deps = [ + "//lib/typescript/model", + "//lib/typescript/types", +] + ts_web_library( name = "httpclient", - deps = [ - "//lib/typescript/model", - "//lib/typescript/types", + deps = module_deps + [ "@npm//@types/node", "@npm//camelcase-keys", ], ) +web_library( + name = "dist", + app_lib = ":httpclient", + entry = "lib/typescript/httpclient/index.js", + externals = { + "@types/node": "@types/node", + "camelcase-keys": "camelcase-keys", + }, + module_deps = module_deps, + output = { + "library": "@airyhq/http-client", + "globalObject": "this", + "libraryTarget": "umd", + "filename": "index.js", + }, +) + +genrule( + name = "npm_library", + srcs = [ + "package.json", + "README.md", + ":dist", + ":httpclient", + ], + outs = ["httpclient_lib"], + cmd = """ + mkdir -p $(OUTS)/{dist} && cp -R $(location :dist) $(OUTS) \ + && cp $(location :package.json) $(location :README.md) $(OUTS) \ + && mv $(RULEDIR)/src $(OUTS) +""", +) + +assemble_npm( + name = "assemble-npm", + target = ":npm_library", + version_file = "//:VERSION", +) + +deploy_npm( + name = "publish-npm", + release = "https://registry.npmjs.org/", + snapshot = "https://registry.npmjs.org/", + target = ":assemble-npm", +) + check_pkg(name = "buildifier") web_lint() diff --git a/lib/typescript/httpclient/client.ts b/lib/typescript/httpclient/client.ts index 94d4353d7a..2d789c3a94 100644 --- a/lib/typescript/httpclient/client.ts +++ b/lib/typescript/httpclient/client.ts @@ -16,7 +16,7 @@ import { UpdateChannelRequestPayload, ListTemplatesRequestPayload, PaginatedResponse, -} from './payload'; +} from './src/payload'; import { listChannelsDef, listConversationsDef, @@ -40,7 +40,7 @@ import { sendMessagesDef, getConfigDef, listTemplatesDef, -} from './endpoints'; +} from './src/endpoints'; function isString(object: any) { return typeof object === 'string' || object instanceof String; diff --git a/lib/typescript/httpclient/index.ts b/lib/typescript/httpclient/index.ts index f00d65a2ea..1eb8bf1257 100644 --- a/lib/typescript/httpclient/index.ts +++ b/lib/typescript/httpclient/index.ts @@ -1,4 +1,2 @@ -export * from './endpoints'; +export * from './src'; export * from './client'; -export * from './payload'; -export * from './messagesForChannels'; diff --git a/lib/typescript/httpclient/package.json b/lib/typescript/httpclient/package.json new file mode 100644 index 0000000000..048753c7ad --- /dev/null +++ b/lib/typescript/httpclient/package.json @@ -0,0 +1,25 @@ +{ + "name": "@airyhq/http-client", + "version": "0.1.0", + "description": "Airy http client library", + "main": "dist/index.js", + "browser": { + "./dist/index.js": "./dist/index.browser.js", + "./dist/index.esm.js": "./dist/index.browser.esm.js" + }, + "typings": "index.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/airyhq/airy.git" + }, + "author": "Airy, Inc.", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/airyhq/airy/issues" + }, + "homepage": "https://github.com/airyhq/airy#readme", + "dependencies": { + "@types/node": "^14.14.37", + "camelcase-keys": "^6.2.2" + } +} diff --git a/lib/typescript/httpclient/endpoints/connectChatPluginChannel.ts b/lib/typescript/httpclient/src/endpoints/connectChatPluginChannel.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/connectChatPluginChannel.ts rename to lib/typescript/httpclient/src/endpoints/connectChatPluginChannel.ts diff --git a/lib/typescript/httpclient/endpoints/connectFacebookChannel.ts b/lib/typescript/httpclient/src/endpoints/connectFacebookChannel.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/connectFacebookChannel.ts rename to lib/typescript/httpclient/src/endpoints/connectFacebookChannel.ts diff --git a/lib/typescript/httpclient/endpoints/connectTwilioSmsChannel.ts b/lib/typescript/httpclient/src/endpoints/connectTwilioSmsChannel.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/connectTwilioSmsChannel.ts rename to lib/typescript/httpclient/src/endpoints/connectTwilioSmsChannel.ts diff --git a/lib/typescript/httpclient/endpoints/connectTwilioWhatsappChannel.ts b/lib/typescript/httpclient/src/endpoints/connectTwilioWhatsappChannel.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/connectTwilioWhatsappChannel.ts rename to lib/typescript/httpclient/src/endpoints/connectTwilioWhatsappChannel.ts diff --git a/lib/typescript/httpclient/endpoints/createTag.ts b/lib/typescript/httpclient/src/endpoints/createTag.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/createTag.ts rename to lib/typescript/httpclient/src/endpoints/createTag.ts diff --git a/lib/typescript/httpclient/endpoints/deleteTag.ts b/lib/typescript/httpclient/src/endpoints/deleteTag.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/deleteTag.ts rename to lib/typescript/httpclient/src/endpoints/deleteTag.ts diff --git a/lib/typescript/httpclient/endpoints/disconnectChannel.ts b/lib/typescript/httpclient/src/endpoints/disconnectChannel.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/disconnectChannel.ts rename to lib/typescript/httpclient/src/endpoints/disconnectChannel.ts diff --git a/lib/typescript/httpclient/endpoints/exploreFacebookChannels.ts b/lib/typescript/httpclient/src/endpoints/exploreFacebookChannels.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/exploreFacebookChannels.ts rename to lib/typescript/httpclient/src/endpoints/exploreFacebookChannels.ts diff --git a/lib/typescript/httpclient/endpoints/getConfig.ts b/lib/typescript/httpclient/src/endpoints/getConfig.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/getConfig.ts rename to lib/typescript/httpclient/src/endpoints/getConfig.ts diff --git a/lib/typescript/httpclient/endpoints/getConversationInfo.ts b/lib/typescript/httpclient/src/endpoints/getConversationInfo.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/getConversationInfo.ts rename to lib/typescript/httpclient/src/endpoints/getConversationInfo.ts diff --git a/lib/typescript/httpclient/endpoints/index.ts b/lib/typescript/httpclient/src/endpoints/index.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/index.ts rename to lib/typescript/httpclient/src/endpoints/index.ts diff --git a/lib/typescript/httpclient/endpoints/listChannels.ts b/lib/typescript/httpclient/src/endpoints/listChannels.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/listChannels.ts rename to lib/typescript/httpclient/src/endpoints/listChannels.ts diff --git a/lib/typescript/httpclient/endpoints/listConversations.ts b/lib/typescript/httpclient/src/endpoints/listConversations.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/listConversations.ts rename to lib/typescript/httpclient/src/endpoints/listConversations.ts diff --git a/lib/typescript/httpclient/endpoints/listMessages.ts b/lib/typescript/httpclient/src/endpoints/listMessages.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/listMessages.ts rename to lib/typescript/httpclient/src/endpoints/listMessages.ts diff --git a/lib/typescript/httpclient/endpoints/listTags.ts b/lib/typescript/httpclient/src/endpoints/listTags.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/listTags.ts rename to lib/typescript/httpclient/src/endpoints/listTags.ts diff --git a/lib/typescript/httpclient/endpoints/listTemplates.ts b/lib/typescript/httpclient/src/endpoints/listTemplates.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/listTemplates.ts rename to lib/typescript/httpclient/src/endpoints/listTemplates.ts diff --git a/lib/typescript/httpclient/endpoints/loginViaEmail.ts b/lib/typescript/httpclient/src/endpoints/loginViaEmail.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/loginViaEmail.ts rename to lib/typescript/httpclient/src/endpoints/loginViaEmail.ts diff --git a/lib/typescript/httpclient/endpoints/readConversations.ts b/lib/typescript/httpclient/src/endpoints/readConversations.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/readConversations.ts rename to lib/typescript/httpclient/src/endpoints/readConversations.ts diff --git a/lib/typescript/httpclient/endpoints/sendMessages.ts b/lib/typescript/httpclient/src/endpoints/sendMessages.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/sendMessages.ts rename to lib/typescript/httpclient/src/endpoints/sendMessages.ts diff --git a/lib/typescript/httpclient/endpoints/tagConversation.ts b/lib/typescript/httpclient/src/endpoints/tagConversation.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/tagConversation.ts rename to lib/typescript/httpclient/src/endpoints/tagConversation.ts diff --git a/lib/typescript/httpclient/endpoints/untagConversation.ts b/lib/typescript/httpclient/src/endpoints/untagConversation.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/untagConversation.ts rename to lib/typescript/httpclient/src/endpoints/untagConversation.ts diff --git a/lib/typescript/httpclient/endpoints/updateChannel.ts b/lib/typescript/httpclient/src/endpoints/updateChannel.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/updateChannel.ts rename to lib/typescript/httpclient/src/endpoints/updateChannel.ts diff --git a/lib/typescript/httpclient/endpoints/updateTag.ts b/lib/typescript/httpclient/src/endpoints/updateTag.ts similarity index 100% rename from lib/typescript/httpclient/endpoints/updateTag.ts rename to lib/typescript/httpclient/src/endpoints/updateTag.ts diff --git a/lib/typescript/httpclient/src/index.ts b/lib/typescript/httpclient/src/index.ts new file mode 100644 index 0000000000..9dad2435c9 --- /dev/null +++ b/lib/typescript/httpclient/src/index.ts @@ -0,0 +1,3 @@ +export * from './endpoints'; +export * from './payload'; +export * from './messagesForChannels'; diff --git a/lib/typescript/httpclient/messagesForChannels/index.ts b/lib/typescript/httpclient/src/messagesForChannels/index.ts similarity index 100% rename from lib/typescript/httpclient/messagesForChannels/index.ts rename to lib/typescript/httpclient/src/messagesForChannels/index.ts diff --git a/lib/typescript/httpclient/messagesForChannels/text/index.ts b/lib/typescript/httpclient/src/messagesForChannels/text/index.ts similarity index 100% rename from lib/typescript/httpclient/messagesForChannels/text/index.ts rename to lib/typescript/httpclient/src/messagesForChannels/text/index.ts diff --git a/lib/typescript/httpclient/payload/ConnectChannelFacebookRequestPayload.ts b/lib/typescript/httpclient/src/payload/ConnectChannelFacebookRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ConnectChannelFacebookRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ConnectChannelFacebookRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ConnectChannelRequestPayload.ts b/lib/typescript/httpclient/src/payload/ConnectChannelRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ConnectChannelRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ConnectChannelRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ConnectChatPluginRequestPayload.ts b/lib/typescript/httpclient/src/payload/ConnectChatPluginRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ConnectChatPluginRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ConnectChatPluginRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ConnectTwilioSmsRequestPayload.ts b/lib/typescript/httpclient/src/payload/ConnectTwilioSmsRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ConnectTwilioSmsRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ConnectTwilioSmsRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ConnectTwilioWhatsappRequestPayload.ts b/lib/typescript/httpclient/src/payload/ConnectTwilioWhatsappRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ConnectTwilioWhatsappRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ConnectTwilioWhatsappRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/CreateTagRequestPayload.ts b/lib/typescript/httpclient/src/payload/CreateTagRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/CreateTagRequestPayload.ts rename to lib/typescript/httpclient/src/payload/CreateTagRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/DisconnectChannelRequestPayload.ts b/lib/typescript/httpclient/src/payload/DisconnectChannelRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/DisconnectChannelRequestPayload.ts rename to lib/typescript/httpclient/src/payload/DisconnectChannelRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ExploreChannelRequestPayload.ts b/lib/typescript/httpclient/src/payload/ExploreChannelRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ExploreChannelRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ExploreChannelRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ListConversationsRequestPayload.ts b/lib/typescript/httpclient/src/payload/ListConversationsRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ListConversationsRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ListConversationsRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ListMessagesRequestPayload.ts b/lib/typescript/httpclient/src/payload/ListMessagesRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ListMessagesRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ListMessagesRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/ListTemplatesRequestPayload.ts b/lib/typescript/httpclient/src/payload/ListTemplatesRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/ListTemplatesRequestPayload.ts rename to lib/typescript/httpclient/src/payload/ListTemplatesRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/LoginViaEmailRequestPayload.ts b/lib/typescript/httpclient/src/payload/LoginViaEmailRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/LoginViaEmailRequestPayload.ts rename to lib/typescript/httpclient/src/payload/LoginViaEmailRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/PaginatedPayload.ts b/lib/typescript/httpclient/src/payload/PaginatedPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/PaginatedPayload.ts rename to lib/typescript/httpclient/src/payload/PaginatedPayload.ts diff --git a/lib/typescript/httpclient/payload/PaginatedResponse.ts b/lib/typescript/httpclient/src/payload/PaginatedResponse.ts similarity index 100% rename from lib/typescript/httpclient/payload/PaginatedResponse.ts rename to lib/typescript/httpclient/src/payload/PaginatedResponse.ts diff --git a/lib/typescript/httpclient/payload/SendMessagesRequestPayload.ts b/lib/typescript/httpclient/src/payload/SendMessagesRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/SendMessagesRequestPayload.ts rename to lib/typescript/httpclient/src/payload/SendMessagesRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/TagConversationRequestPayload.ts b/lib/typescript/httpclient/src/payload/TagConversationRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/TagConversationRequestPayload.ts rename to lib/typescript/httpclient/src/payload/TagConversationRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/UntagConversationRequestPayload.ts b/lib/typescript/httpclient/src/payload/UntagConversationRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/UntagConversationRequestPayload.ts rename to lib/typescript/httpclient/src/payload/UntagConversationRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/UpdateChannelRequestPayload.ts b/lib/typescript/httpclient/src/payload/UpdateChannelRequestPayload.ts similarity index 100% rename from lib/typescript/httpclient/payload/UpdateChannelRequestPayload.ts rename to lib/typescript/httpclient/src/payload/UpdateChannelRequestPayload.ts diff --git a/lib/typescript/httpclient/payload/index.ts b/lib/typescript/httpclient/src/payload/index.ts similarity index 100% rename from lib/typescript/httpclient/payload/index.ts rename to lib/typescript/httpclient/src/payload/index.ts diff --git a/tools/build/npm/BUILD b/tools/build/npm/BUILD new file mode 100644 index 0000000000..93bcbca1ba --- /dev/null +++ b/tools/build/npm/BUILD @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "lib", + srcs = [ + "rules.bzl", + ], + visibility = ["//visibility:public"], +) + +py_binary( + name = "assemble", + srcs = ["assemble.py"], + visibility = ["//visibility:public"], +) diff --git a/tools/build/npm/README.md b/tools/build/npm/README.md new file mode 100644 index 0000000000..bec9efa25e --- /dev/null +++ b/tools/build/npm/README.md @@ -0,0 +1,3 @@ +Npm deployment tools + +These Bazel rules were copied and adapted under the Apache 2.0 license from the excellent bazel distribution ruleset by https://github.com/graknlabs. \ No newline at end of file diff --git a/tools/build/npm/assemble.py b/tools/build/npm/assemble.py new file mode 100644 index 0000000000..ad138d23b7 --- /dev/null +++ b/tools/build/npm/assemble.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +import argparse +import glob +import json +import shutil +import subprocess +import os +import tempfile + +parser = argparse.ArgumentParser() +parser.add_argument('--package', help="NPM package to pack") +parser.add_argument('--version_file', help="Version file") +parser.add_argument('--output', help="Output archive") + +args = parser.parse_args() + +with open(args.version_file) as version_file: + version = version_file.read().strip() + +new_package_root = tempfile.mktemp() + +shutil.copytree(args.package, new_package_root, + ignore=lambda _, names: list( + filter(lambda x: 'external' in x, names))) +package_json_fn = os.path.join(new_package_root, 'package.json') + +with open(package_json_fn) as f: + package_json = json.load(f) + +package_json['version'] = version + +os.chmod(package_json_fn, 0o755) + +with open(package_json_fn, 'w') as f: + json.dump(package_json, f) + + +os.chmod(new_package_root, 0o755) +for root, dirs, files in os.walk(new_package_root): + for d in dirs: + os.chmod(os.path.join(root, d), 0o755) + for f in files: + os.chmod(os.path.join(root, f), 0o755) + +subprocess.check_call([ + 'npm', + 'pack' +], env={ + 'PATH': ':'.join([ + '/usr/bin/', + '/bin/', + os.path.realpath('external/nodejs/bin/nodejs/bin/'), + os.path.realpath('external/nodejs_darwin_amd64/bin/'), + os.path.realpath('external/nodejs_linux_amd64/bin/'), + os.path.realpath('external/nodejs_windows_amd64/bin/'), + ]) +}, cwd=new_package_root) + +archives = glob.glob(os.path.join(new_package_root, '*.tgz')) +if len(archives) != 1: + raise Exception('expected one archive instead of {}'.format(archives)) + +shutil.copy(archives[0], args.output) +shutil.rmtree(new_package_root) diff --git a/tools/build/npm/rules.bzl b/tools/build/npm/rules.bzl new file mode 100644 index 0000000000..652cfb688e --- /dev/null +++ b/tools/build/npm/rules.bzl @@ -0,0 +1,136 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +def _assemble_npm_impl(ctx): + if len(ctx.files.target) != 1: + fail("target contains more files than expected") + + if not ctx.attr.version_file: + version_file = ctx.actions.declare_file(ctx.attr.name + "__do_not_reference.version") + version = ctx.var.get("version", "0.0.0") + + if len(version) == 40: + # this is a commit SHA, most likely + version = "0.0.0-{}".format(version) + + ctx.actions.run_shell( + inputs = [], + outputs = [version_file], + command = "echo {} > {}".format(version, version_file.path), + ) + else: + version_file = ctx.file.version_file + + args = ctx.actions.args() + args.add("--package", ctx.files.target[0].path) + args.add("--output", ctx.outputs.npm_package.path) + args.add("--version_file", version_file.path) + + ctx.actions.run( + inputs = ctx.files.target + ctx.files._npm + [version_file], + outputs = [ctx.outputs.npm_package], + arguments = [args], + executable = ctx.executable._assemble_script, + # note: do not run in RBE + ) + +assemble_npm = rule( + implementation = _assemble_npm_impl, + attrs = { + "target": attr.label( + mandatory = True, + doc = "`npm_library` label to be included in the package", + ), + "version_file": attr.label( + allow_single_file = True, + doc = """ + File containing version string. + Alternatively, pass --define version=VERSION to Bazel invocation. + Not specifying version at all defaults to '0.0.0' + """, + ), + "_assemble_script": attr.label( + default = "//tools/build/npm:assemble", + executable = True, + cfg = "host", + ), + "_npm": attr.label( + default = Label("@nodejs//:npm"), + allow_files = True, + ), + }, + outputs = { + "npm_package": "%{name}.tar.gz", + }, + doc = "Assemble `npm_package` target for further deployment. Currently does not support remote execution (RBE).", +) + +def _deploy_npm(ctx): + ctx.actions.expand_template( + template = ctx.file._deployment_script_template, + output = ctx.outputs.executable, + substitutions = { + "{snapshot}": ctx.attr.snapshot, + "{release}": ctx.attr.release, + }, + is_executable = True, + ) + + files = [ + ctx.file.target, + ] + files.extend(ctx.files._npm) + + return DefaultInfo( + executable = ctx.outputs.executable, + runfiles = ctx.runfiles( + files = files, + symlinks = { + "deploy_npm.tgz": ctx.file.target, + }, + ), + ) + +deploy_npm = rule( + implementation = _deploy_npm, + executable = True, + attrs = { + "target": attr.label( + mandatory = True, + allow_single_file = True, + doc = "`assemble_npm` label to be included in the package", + ), + "snapshot": attr.string( + mandatory = True, + doc = "Snapshot repository to deploy npm artifact to", + ), + "release": attr.string( + mandatory = True, + doc = "Release repository to deploy npm artifact to", + ), + "_deployment_script_template": attr.label( + allow_single_file = True, + default = "//tools/build/npm/templates:deploy.py", + ), + "_npm": attr.label( + default = Label("@nodejs//:npm"), + allow_files = True, + ), + }, +) diff --git a/tools/build/npm/templates/BUILD b/tools/build/npm/templates/BUILD new file mode 100644 index 0000000000..6c1b1ff1db --- /dev/null +++ b/tools/build/npm/templates/BUILD @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +exports_files(["deploy.py"]) diff --git a/tools/build/npm/templates/deploy.py b/tools/build/npm/templates/deploy.py new file mode 100644 index 0000000000..10a8fe02c4 --- /dev/null +++ b/tools/build/npm/templates/deploy.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +import argparse +import os +import subprocess +import shutil +import re + +# usual importing is not possible because +# this script and module with common functions +# are at different directory levels in sandbox +import tempfile + +parser = argparse.ArgumentParser() +parser.add_argument('repo_type') +args = parser.parse_args() + +repo_type = args.repo_type +npm_repositories = { + "snapshot": "{snapshot}", + "release": "{release}", +} +npm_registry = npm_repositories[repo_type] + +npm_username, npm_password, npm_email = ( + os.getenv('DEPLOY_NPM_USERNAME'), + os.getenv('DEPLOY_NPM_PASSWORD'), + os.getenv('DEPLOY_NPM_EMAIL'), +) + +if not npm_username: + raise Exception( + 'username should be passed via ' + '$DEPLOY_NPM_USERNAME env variable' + ) + +if not npm_password: + raise Exception( + 'password should be passed via ' + '$DEPLOY_NPM_PASSWORD env variable' + ) + +if not npm_email: + raise Exception( + 'email should be passed via ' + '$DEPLOY_NPM_EMAIL env variable' + ) + +expect_input_tmpl = '''spawn npm adduser --registry={registry} +expect {{ + "Username:" {{send "{username}\r"; exp_continue}} + "Password:" {{send "$env(PASSWORD)\r"; exp_continue}} + "Email: (this IS public)" {{send "{email}\r"; exp_continue}} +}}''' + +with tempfile.NamedTemporaryFile('wt', delete=False) as expect_input_file: + expect_input_file.write(expect_input_tmpl.format( + registry=npm_registry, + username=npm_username, + email=npm_email, + )) + +node_path = ':'.join([ + '/usr/bin/', + '/bin/', + os.path.realpath('external/nodejs/bin/nodejs/bin/'), + os.path.realpath('external/nodejs_darwin_amd64/bin/'), + os.path.realpath('external/nodejs_linux_amd64/bin/'), + os.path.realpath('external/nodejs_windows_amd64/bin/'), +]) + +with open(expect_input_file.name) as expect_input: + subprocess.check_call([ + '/usr/bin/expect', + ], stdin=expect_input, env={ + 'PATH': node_path, + 'PASSWORD': npm_password + }) + +subprocess.check_call([ + 'npm', + 'publish', + '--registry={}'.format(npm_registry), + 'deploy_npm.tgz' +], env={ + 'PATH': node_path +}) From 3134ff52ef6a9597695425e7f3f65d0c404eb604 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Apr 2021 09:51:52 +0200 Subject: [PATCH 22/50] Bump @types/node from 14.14.40 to 14.14.41 (#1561) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.40 to 14.14.41. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2c2ee8f596..0bb9ca2772 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@crello/react-lottie": "^0.0.11", "@reduxjs/toolkit": "^1.5.1", "@stomp/stompjs": "^6.1.0", - "@types/node": "14.14.40", + "@types/node": "14.14.41", "@types/react": "16.9.34", "@types/react-dom": "16.9.2", "@types/react-redux": "7.1.16", diff --git a/yarn.lock b/yarn.lock index a3cf751ae8..f6c2aab051 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1358,10 +1358,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== -"@types/node@*", "@types/node@14.14.40", "@types/node@^14.14.31": - version "14.14.40" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.40.tgz#05a7cd31154487f357ca0bec4334ed1b1ab825a0" - integrity sha512-2HoZZGylcnz19ZSbvWhgWHqvprw1ZGHanxIrDWYykPD4CauLW4gcyLzCVfUN2kv/1t1F3CurQIdi+s1l9+XgEA== +"@types/node@*", "@types/node@14.14.41", "@types/node@^14.14.31": + version "14.14.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615" + integrity sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g== "@types/node@^10.1.0": version "10.17.55" From 5df82d4f59a48f523c538b0128472dc6fa290133 Mon Sep 17 00:00:00 2001 From: Aitor Algorta Date: Fri, 16 Apr 2021 09:53:37 +0200 Subject: [PATCH 23/50] [#1502] Improve model lib (#1547) * remove not necessary type * duplicate right components * remove default props * linting --- .../chat-plugin/src/components/chat/index.tsx | 19 ++++-- .../Inbox/Messenger/MessageList/index.tsx | 6 +- lib/typescript/model/Content.ts | 18 +---- lib/typescript/model/Message.ts | 8 ++- lib/typescript/model/SuggestedReply.ts | 5 +- lib/typescript/model/Template.ts | 4 +- .../components/MessageInfoWrapper/index.tsx | 4 +- .../components/RichCard/Media/index.tsx | 23 ------- .../render/components/Text/index.tsx | 4 +- lib/typescript/render/components/index.ts | 3 - lib/typescript/render/index.tsx | 4 +- lib/typescript/render/props.ts | 14 +--- .../providers/chatplugin/ChatPluginRender.tsx | 17 ++--- .../providers/chatplugin/chatPluginModel.ts | 20 +++++- .../components/QuickReplies/index.tsx | 8 ++- .../RichCard/Media/index.module.scss | 0 .../components/RichCard/Media/index.tsx | 32 +++++++++ .../components/RichCard/index.module.scss | 0 .../chatplugin}/components/RichCard/index.tsx | 28 ++------ .../RichCardCarousel/index.module.scss | 0 .../components/RichCardCarousel/index.tsx | 46 +++++++++++++ .../components/RichText/index.module.scss | 0 .../chatplugin}/components/RichText/index.tsx | 10 +-- .../providers/facebook/FacebookRender.tsx | 18 ++--- .../components/ButtonTemplate/index.tsx | 4 +- .../components/GenericTemplate/index.tsx | 4 +- .../components/QuickReplies/index.tsx | 8 ++- .../render/providers/google/GoogleRender.tsx | 26 ++++---- .../RichCard/Media/index.module.scss | 19 ++++++ .../components/RichCard/Media/index.tsx | 32 +++++++++ .../components/RichCard/index.module.scss | 64 ++++++++++++++++++ .../google/components/RichCard/index.tsx | 66 +++++++++++++++++++ .../RichCardCarousel/index.module.scss | 6 ++ .../components/RichCardCarousel/index.tsx | 11 ++-- .../components/RichText/index.module.scss | 49 ++++++++++++++ .../google/components/RichText/index.tsx | 25 +++++++ .../google/components/Suggestions/index.tsx | 9 ++- .../render/providers/google/googleModel.ts | 21 +++++- .../twilio/twilioSMS/TwilioSMSRender.tsx | 14 ++-- .../twilioWhatsapp/TwilioWhatsappRender.tsx | 14 ++-- lib/typescript/websocketclient/payload.ts | 4 +- 41 files changed, 500 insertions(+), 167 deletions(-) delete mode 100644 lib/typescript/render/components/RichCard/Media/index.tsx delete mode 100644 lib/typescript/render/components/index.ts rename lib/typescript/render/{ => providers/chatplugin}/components/RichCard/Media/index.module.scss (100%) create mode 100644 lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx rename lib/typescript/render/{ => providers/chatplugin}/components/RichCard/index.module.scss (100%) rename lib/typescript/render/{ => providers/chatplugin}/components/RichCard/index.tsx (82%) rename lib/typescript/render/{ => providers/chatplugin}/components/RichCardCarousel/index.module.scss (100%) create mode 100644 lib/typescript/render/providers/chatplugin/components/RichCardCarousel/index.tsx rename lib/typescript/render/{ => providers/chatplugin}/components/RichText/index.module.scss (100%) rename lib/typescript/render/{ => providers/chatplugin}/components/RichText/index.tsx (76%) create mode 100644 lib/typescript/render/providers/google/components/RichCard/Media/index.module.scss create mode 100644 lib/typescript/render/providers/google/components/RichCard/Media/index.tsx create mode 100644 lib/typescript/render/providers/google/components/RichCard/index.module.scss create mode 100644 lib/typescript/render/providers/google/components/RichCard/index.tsx create mode 100644 lib/typescript/render/providers/google/components/RichCardCarousel/index.module.scss rename lib/typescript/render/{ => providers/google}/components/RichCardCarousel/index.tsx (85%) create mode 100644 lib/typescript/render/providers/google/components/RichText/index.module.scss create mode 100644 lib/typescript/render/providers/google/components/RichText/index.tsx diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index e4beade46a..4ef8e8440d 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -2,20 +2,23 @@ import React from 'react'; import {useState, useEffect} from 'react'; import {IMessage} from '@stomp/stompjs'; +import {AiryWidgetConfiguration} from '../../config'; +import {DeliveryState, Message} from 'model'; + import WebSocket, {ConnectionState} from '../../websocket'; import MessageProp from '../../components/message'; import InputBarProp from '../../components/inputBar'; import AiryInputBar from '../../airyRenderProps/AiryInputBar'; -import style from './index.module.scss'; import HeaderBarProp from '../../components/headerBar'; import AiryHeaderBar from '../../airyRenderProps/AiryHeaderBar'; -import {AiryWidgetConfiguration} from '../../config'; + import BubbleProp from '../bubble'; import AiryBubble from '../../airyRenderProps/AiryBubble'; -import {MessageState, isFromContact, Message} from 'model'; + import {SourceMessage, CommandUnion} from 'render'; import {MessageInfoWrapper} from 'render/components/MessageInfoWrapper'; + /* eslint-disable @typescript-eslint/no-var-requires */ const camelcaseKeys = require('camelcase-keys'); import {cyBubble, cyChatPluginMessageList, cyChatPluginEndChatModalButton} from 'chat-plugin-handles'; @@ -24,12 +27,14 @@ import {ModalDialogue} from '../../components/modal'; import NewConversation from '../../components/newConversation'; import {start} from '../../api'; +import style from './index.module.scss'; + let ws: WebSocket; const defaultWelcomeMessage: Message = { id: '19527d24-9b47-4e18-9f79-fd1998b95059', content: {text: 'Hello! How can we help you?'}, - deliveryState: MessageState.delivered, + deliveryState: DeliveryState.delivered, fromContact: false, sentAt: new Date(), }; @@ -197,9 +202,9 @@ const Chat = (props: Props) => {

- {messages.map((message, index: number) => { + {messages.map((message: Message, index: number) => { const nextMessage = messages[index + 1]; - const lastInGroup = nextMessage ? isFromContact(message) !== isFromContact(nextMessage) : true; + const lastInGroup = nextMessage ? message.fromContact !== nextMessage.fromContact : true; return ( { ? () => props.airyMessageProp(ctrl) : () => ( { const prevMessage = messages[index - 1]; const nextMessage = messages[index + 1]; - const lastInGroup = nextMessage ? isFromContact(message) !== isFromContact(nextMessage) : true; + const lastInGroup = nextMessage ? message.fromContact !== nextMessage.fromContact : true; const sentAt = lastInGroup ? formatTime(message.sentAt) : null; @@ -190,7 +190,7 @@ const MessageList = (props: MessageListProps) => {
)} { const {sentAt, contact, fromContact, children, lastInGroup, isChatPlugin, decoration} = props; diff --git a/lib/typescript/render/components/RichCard/Media/index.tsx b/lib/typescript/render/components/RichCard/Media/index.tsx deleted file mode 100644 index 328fa0379a..0000000000 --- a/lib/typescript/render/components/RichCard/Media/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import styles from './index.module.scss'; -import {MediaHeight} from '../../../providers/chatplugin/chatPluginModel'; -import {ImageWithFallback} from 'render/components/ImageWithFallback'; - -export type MediaRenderProps = { - height: MediaHeight; - contentInfo: { - altText?: string; - fileUrl: string; - forceRefresh: boolean; - }; -}; - -export const Media = ({height, contentInfo: {altText, fileUrl}}: MediaRenderProps) => ( - -); diff --git a/lib/typescript/render/components/Text/index.tsx b/lib/typescript/render/components/Text/index.tsx index 81ceefcb0d..b0a92c6a63 100644 --- a/lib/typescript/render/components/Text/index.tsx +++ b/lib/typescript/render/components/Text/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import styles from './index.module.scss'; -import {DefaultRenderingProps} from '../index'; -type TextRenderProps = DefaultRenderingProps & { +type TextRenderProps = { text: string; + fromContact?: boolean; }; export const Text = ({text, fromContact}: TextRenderProps) => ( diff --git a/lib/typescript/render/components/index.ts b/lib/typescript/render/components/index.ts deleted file mode 100644 index 0d6cae2f1a..0000000000 --- a/lib/typescript/render/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface DefaultRenderingProps { - fromContact?: boolean; -} diff --git a/lib/typescript/render/index.tsx b/lib/typescript/render/index.tsx index 04e8171d27..41f91d72ec 100644 --- a/lib/typescript/render/index.tsx +++ b/lib/typescript/render/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {renderProviders} from './renderProviders'; import {Text} from './components/Text'; -import {getDefaultRenderingProps, RenderPropsUnion} from './props'; +import {RenderPropsUnion} from './props'; export * from './props'; @@ -25,7 +25,7 @@ export class SourceMessage extends React.Component; + return ; } render() { diff --git a/lib/typescript/render/props.ts b/lib/typescript/render/props.ts index f47de83bb6..deb1482166 100644 --- a/lib/typescript/render/props.ts +++ b/lib/typescript/render/props.ts @@ -1,6 +1,4 @@ -import {isFromContact, RenderedContentUnion} from 'model'; -import {DefaultRenderingProps} from './components'; - +import {Content} from 'model'; export interface Command { type: string; } @@ -25,7 +23,7 @@ export type CommandUnion = SuggestedReplyCommand | QuickReplyCommand; interface RenderProps { contentType: 'message' | 'template' | 'suggestedReplies' | 'quickReplies'; - content: RenderedContentUnion; + content: Content; source: string; } @@ -51,11 +49,3 @@ export type RenderPropsUnion = | TemplateRenderProps | SuggestedRepliesRenderProps | QuickRepliesRenderProps; - -export const getDefaultRenderingProps = (props: RenderPropsUnion): DefaultRenderingProps => { - const fromContact = isFromContact(props.content); - - return { - fromContact, - }; -}; diff --git a/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx b/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx index 25d1634993..8dcfe702f1 100644 --- a/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx +++ b/lib/typescript/render/providers/chatplugin/ChatPluginRender.tsx @@ -1,12 +1,13 @@ import React from 'react'; -import {getDefaultRenderingProps, RenderPropsUnion} from '../../props'; -import {RichText} from '../../components/RichText'; -import {RichCard} from '../../components/RichCard'; -import {RichCardCarousel} from '../../components/RichCardCarousel'; -import {Text} from '../../components/Text'; + +import {RenderPropsUnion} from '../../props'; import {AttachmentUnion, ContentUnion, SimpleAttachment} from './chatPluginModel'; +import {Message} from 'model'; +import {Text} from '../../components/Text'; +import {RichText} from './components/RichText'; +import {RichCard} from './components/RichCard'; +import {RichCardCarousel} from './components/RichCardCarousel'; import {QuickReplies} from './components/QuickReplies/index'; -import {RenderedContentUnion} from 'model'; export const ChatPluginRender = (props: RenderPropsUnion) => { return render(mapContent(props.content), props); @@ -14,7 +15,7 @@ export const ChatPluginRender = (props: RenderPropsUnion) => { function render(content: ContentUnion, props: RenderPropsUnion) { const defaultProps = { - ...getDefaultRenderingProps(props), + fromContact: props.content.fromContact || false, commandCallback: 'commandCallback' in props ? props.commandCallback : null, }; const invertedProps = {...defaultProps, fromContact: !defaultProps.fromContact}; @@ -59,7 +60,7 @@ function render(content: ContentUnion, props: RenderPropsUnion) { } } -function mapContent(message: RenderedContentUnion): ContentUnion { +function mapContent(message: Message): ContentUnion { const messageContent = message.content.message ?? message.content; if (messageContent.quick_replies) { diff --git a/lib/typescript/render/providers/chatplugin/chatPluginModel.ts b/lib/typescript/render/providers/chatplugin/chatPluginModel.ts index e930391474..d3a5bf07a0 100644 --- a/lib/typescript/render/providers/chatplugin/chatPluginModel.ts +++ b/lib/typescript/render/providers/chatplugin/chatPluginModel.ts @@ -1,4 +1,3 @@ -import {Suggestion} from '../../components/RichCard'; export interface Content { type: 'text' | 'image' | 'video' | 'richText' | 'richCard' | 'richCardCarousel' | 'quickReplies'; } @@ -44,9 +43,26 @@ export interface RichCardContent extends Content { forceRefresh: boolean; }; }; - suggestions: Suggestion[]; + suggestions: RichCardSuggestion[]; } +export type RichCardSuggestion = { + reply?: { + text: string; + postbackData: string; + }; + action?: { + text: string; + postbackData: string; + openUrlAction?: { + url: string; + }; + dialAction?: { + phoneNumber: string; + }; + }; +}; + export interface RichCardCarouselContent extends Content { type: 'richCardCarousel'; cardWidth: string; diff --git a/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx b/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx index 185c2c1a12..9ea497b396 100644 --- a/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx +++ b/lib/typescript/render/providers/chatplugin/components/QuickReplies/index.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import styles from './index.module.scss'; -import {DefaultRenderingProps} from '../../../../components/index'; + import {Text} from '../../../../components/Text'; import {Video} from '../../../../components/Video'; import {Image} from '../../../../components/Image'; @@ -8,9 +7,12 @@ import {ImageWithFallback} from 'render/components/ImageWithFallback'; import {QuickReply, AttachmentUnion} from 'render/providers/chatplugin/chatPluginModel'; import {CommandUnion} from 'render/props'; -export type QuickRepliesRenderProps = DefaultRenderingProps & { +import styles from './index.module.scss'; + +export type QuickRepliesRenderProps = { text?: string; attachment?: AttachmentUnion; + fromContact?: boolean; quickReplies: QuickReply[]; commandCallback?: (command: CommandUnion) => void; }; diff --git a/lib/typescript/render/components/RichCard/Media/index.module.scss b/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.module.scss similarity index 100% rename from lib/typescript/render/components/RichCard/Media/index.module.scss rename to lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.module.scss diff --git a/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx b/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx new file mode 100644 index 0000000000..2e1c798a54 --- /dev/null +++ b/lib/typescript/render/providers/chatplugin/components/RichCard/Media/index.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import {MediaHeight} from '../../../chatPluginModel'; +import {ImageWithFallback} from 'render/components/ImageWithFallback'; + +import styles from './index.module.scss'; + +export type MediaRenderProps = { + height: MediaHeight; + contentInfo: { + altText?: string; + fileUrl: string; + forceRefresh: boolean; + }; +}; + +const getHeight = (height: MediaHeight): string => { + switch (height) { + case MediaHeight.short: + return styles.short; + case MediaHeight.medium: + return styles.medium; + case MediaHeight.tall: + return styles.tall; + default: + return styles.medium; + } +}; + +export const Media = ({height, contentInfo: {altText, fileUrl}}: MediaRenderProps) => ( + +); diff --git a/lib/typescript/render/components/RichCard/index.module.scss b/lib/typescript/render/providers/chatplugin/components/RichCard/index.module.scss similarity index 100% rename from lib/typescript/render/components/RichCard/index.module.scss rename to lib/typescript/render/providers/chatplugin/components/RichCard/index.module.scss diff --git a/lib/typescript/render/components/RichCard/index.tsx b/lib/typescript/render/providers/chatplugin/components/RichCard/index.tsx similarity index 82% rename from lib/typescript/render/components/RichCard/index.tsx rename to lib/typescript/render/providers/chatplugin/components/RichCard/index.tsx index 645506b7b1..3133f98a75 100644 --- a/lib/typescript/render/components/RichCard/index.tsx +++ b/lib/typescript/render/providers/chatplugin/components/RichCard/index.tsx @@ -1,36 +1,22 @@ import React from 'react'; -import styles from './index.module.scss'; + import {Media, MediaRenderProps} from './Media'; -import {CommandUnion} from '../../props'; +import {CommandUnion} from '../../../../props'; +import {RichCardSuggestion} from '../../chatPluginModel'; -export type Suggestion = { - reply?: { - text: string; - postbackData: string; - }; - action?: { - text: string; - postbackData: string; - openUrlAction?: { - url: string; - }; - dialAction?: { - phoneNumber: string; - }; - }; -}; +import styles from './index.module.scss'; export type RichCardRenderProps = { title?: string; description?: string; - suggestions: Suggestion[]; + suggestions: RichCardSuggestion[]; media: MediaRenderProps; cardWidth?: string; commandCallback?: (command: CommandUnion) => void; }; export const RichCard = ({title, description, suggestions, media, cardWidth, commandCallback}: RichCardRenderProps) => { - const clickSuggestion = (suggestion: Suggestion) => { + const clickSuggestion = (suggestion: RichCardSuggestion) => { if (suggestion.reply) { commandCallback && commandCallback({ @@ -61,7 +47,7 @@ export const RichCard = ({title, description, suggestions, media, cardWidth, com {title &&

{title}

} {description && {description}}
- {suggestions.map((suggestion: Suggestion, idx: number) => ( + {suggestions.map((suggestion: RichCardSuggestion, idx: number) => ( + ))} +
+
+
+ + ); +}; diff --git a/lib/typescript/render/providers/google/components/RichCardCarousel/index.module.scss b/lib/typescript/render/providers/google/components/RichCardCarousel/index.module.scss new file mode 100644 index 0000000000..ad9f348e95 --- /dev/null +++ b/lib/typescript/render/providers/google/components/RichCardCarousel/index.module.scss @@ -0,0 +1,6 @@ +@import 'assets/scss/fonts.scss'; +@import 'assets/scss/colors.scss'; + +.richCard { + padding-right: 5px; +} diff --git a/lib/typescript/render/components/RichCardCarousel/index.tsx b/lib/typescript/render/providers/google/components/RichCardCarousel/index.tsx similarity index 85% rename from lib/typescript/render/components/RichCardCarousel/index.tsx rename to lib/typescript/render/providers/google/components/RichCardCarousel/index.tsx index 478b9850c8..1da41099fa 100644 --- a/lib/typescript/render/components/RichCardCarousel/index.tsx +++ b/lib/typescript/render/providers/google/components/RichCardCarousel/index.tsx @@ -1,16 +1,19 @@ import React from 'react'; + import {Carousel} from 'components'; -import styles from './index.module.scss'; import {MediaRenderProps} from '../RichCard/Media'; -import {RichCard, Suggestion} from '../RichCard'; -import {CommandUnion} from '../../props'; +import {RichCard} from '../RichCard'; +import {RichCardSuggestion} from '../../googleModel'; +import {CommandUnion} from '../../../../props'; + +import styles from './index.module.scss'; type Card = { id?: string; title?: string; description?: string; media: MediaRenderProps; - suggestions: Suggestion[]; + suggestions: RichCardSuggestion[]; }; export type RichCardCarouselRenderProps = { diff --git a/lib/typescript/render/providers/google/components/RichText/index.module.scss b/lib/typescript/render/providers/google/components/RichText/index.module.scss new file mode 100644 index 0000000000..4ff77b327a --- /dev/null +++ b/lib/typescript/render/providers/google/components/RichText/index.module.scss @@ -0,0 +1,49 @@ +@import 'assets/scss/colors.scss'; + +.contactContent { + width: 100%; + overflow-wrap: break-word; + word-break: break-word; + padding: 10px; + margin-top: 5px; + background: var(--color-background-blue); + color: var(--color-text-contrast); + border-radius: 8px; +} + +.memberContent { + width: 100%; + overflow-wrap: break-word; + word-break: break-word; + padding: 10px; + margin-top: 5px; + background: var(--color-airy-blue); + color: white; + border-radius: 8px; + a { + color: white; + &:visited, + &:hover { + color: white; + } + } +} + +.richText { + p:first-child { + margin-top: 0; + } + p:last-child { + margin-bottom: 0; + } + + b, + strong { + font-weight: 700; + } + + i, + em { + font-style: italic; + } +} diff --git a/lib/typescript/render/providers/google/components/RichText/index.tsx b/lib/typescript/render/providers/google/components/RichText/index.tsx new file mode 100644 index 0000000000..496106943c --- /dev/null +++ b/lib/typescript/render/providers/google/components/RichText/index.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactMarkdown from 'react-markdown'; + +import {Message} from 'model'; + +import styles from './index.module.scss'; + +type RichTextRenderProps = { + message: Message; + text: string; + fromContact?: boolean; + fallback: string; + containsRichText: boolean; +}; + +export const RichText = (props: RichTextRenderProps) => { + const {message, text, fromContact} = props; + return ( +
+ + {text} + +
+ ); +}; diff --git a/lib/typescript/render/providers/google/components/Suggestions/index.tsx b/lib/typescript/render/providers/google/components/Suggestions/index.tsx index 373a4049fa..8b71ae124a 100644 --- a/lib/typescript/render/providers/google/components/Suggestions/index.tsx +++ b/lib/typescript/render/providers/google/components/Suggestions/index.tsx @@ -1,15 +1,18 @@ import React from 'react'; -import styles from './index.module.scss'; -import {DefaultRenderingProps} from '../../../../components/index'; + import {SuggestionsUnion} from '../../googleModel'; import {Image} from '../../../../components/Image'; import {Text} from '../../../../components/Text'; + import linkIcon from 'assets/images/icons/link.svg'; import phoneIcon from 'assets/images/icons/phone.svg'; -type SuggestionsRendererProps = DefaultRenderingProps & { +import styles from './index.module.scss'; + +type SuggestionsRendererProps = { text?: string; fallback?: string; + fromContact?: boolean; image?: { fileUrl: string; altText: string; diff --git a/lib/typescript/render/providers/google/googleModel.ts b/lib/typescript/render/providers/google/googleModel.ts index 6587dfe77d..db37799f97 100644 --- a/lib/typescript/render/providers/google/googleModel.ts +++ b/lib/typescript/render/providers/google/googleModel.ts @@ -1,5 +1,3 @@ -import {Suggestion} from '../../components/RichCard'; - export enum MediaHeight { short = 'SHORT', medium = 'MEDIUM', @@ -31,9 +29,26 @@ export interface RichCardContent extends Content { forceRefresh: boolean; }; }; - suggestions: Suggestion[]; + suggestions: RichCardSuggestion[]; } +export type RichCardSuggestion = { + reply?: { + text: string; + postbackData: string; + }; + action?: { + text: string; + postbackData: string; + openUrlAction?: { + url: string; + }; + dialAction?: { + phoneNumber: string; + }; + }; +}; + export interface RichCardCarouselContent extends Content { type: 'richCardCarousel'; cardWidth: string; diff --git a/lib/typescript/render/providers/twilio/twilioSMS/TwilioSMSRender.tsx b/lib/typescript/render/providers/twilio/twilioSMS/TwilioSMSRender.tsx index c9987487b7..17addfa590 100644 --- a/lib/typescript/render/providers/twilio/twilioSMS/TwilioSMSRender.tsx +++ b/lib/typescript/render/providers/twilio/twilioSMS/TwilioSMSRender.tsx @@ -1,23 +1,23 @@ import React from 'react'; -import {isFromContact, RenderedContentUnion} from 'model'; +import {Message} from 'model'; import {Text} from '../../../components/Text'; -import {getDefaultRenderingProps, RenderPropsUnion} from '../../../props'; +import {RenderPropsUnion} from '../../../props'; import {ContentUnion} from './twilioSMSModel'; export const TwilioSMSRender = (props: RenderPropsUnion) => { - const message = props.content; - const content = isFromContact(message) ? inboundContent(message) : outboundContent(message); + const message: Message = props.content; + const content = message.fromContact ? inboundContent(message) : outboundContent(message); return render(content, props); }; function render(content: ContentUnion, props: RenderPropsUnion) { switch (content.type) { case 'text': - return ; + return ; } } -const inboundContent = (message: RenderedContentUnion): ContentUnion => { +const inboundContent = (message: Message): ContentUnion => { const messageContent = message.content; const startText = messageContent.search('&Body='); const endText = messageContent.search('&FromCountry='); @@ -32,7 +32,7 @@ const inboundContent = (message: RenderedContentUnion): ContentUnion => { }; }; -const outboundContent = (message: RenderedContentUnion): ContentUnion => { +const outboundContent = (message: Message): ContentUnion => { const messageContent = message.content.message ?? message.content; return { type: 'text', diff --git a/lib/typescript/render/providers/twilio/twilioWhatsapp/TwilioWhatsappRender.tsx b/lib/typescript/render/providers/twilio/twilioWhatsapp/TwilioWhatsappRender.tsx index 1ea20ffc00..d6ae8e1a2d 100644 --- a/lib/typescript/render/providers/twilio/twilioWhatsapp/TwilioWhatsappRender.tsx +++ b/lib/typescript/render/providers/twilio/twilioWhatsapp/TwilioWhatsappRender.tsx @@ -1,23 +1,23 @@ import React from 'react'; -import {isFromContact, RenderedContentUnion} from 'model'; +import {Message} from 'model'; import {Text} from '../../../components/Text'; -import {getDefaultRenderingProps, RenderPropsUnion} from '../../../props'; +import {RenderPropsUnion} from '../../../props'; import {ContentUnion} from './twilioWhatsappModel'; export const TwilioWhatsappRender = (props: RenderPropsUnion) => { - const message = props.content; - const content = isFromContact(message) ? inboundContent(message) : outboundContent(message); + const message: Message = props.content; + const content = message.fromContact ? inboundContent(message) : outboundContent(message); return render(content, props); }; function render(content: ContentUnion, props: RenderPropsUnion) { switch (content.type) { case 'text': - return ; + return ; } } -const inboundContent = (message: RenderedContentUnion): ContentUnion => { +const inboundContent = (message: Message): ContentUnion => { const messageContent = message.content; const startText = messageContent.search('&Body='); const endText = messageContent.search('&To=whatsapp'); @@ -32,7 +32,7 @@ const inboundContent = (message: RenderedContentUnion): ContentUnion => { }; }; -const outboundContent = (message: RenderedContentUnion): ContentUnion => { +const outboundContent = (message: Message): ContentUnion => { const messageContent = message.content.message ?? message.content; return { type: 'text', diff --git a/lib/typescript/websocketclient/payload.ts b/lib/typescript/websocketclient/payload.ts index 1a76fb027b..18c989b329 100644 --- a/lib/typescript/websocketclient/payload.ts +++ b/lib/typescript/websocketclient/payload.ts @@ -1,4 +1,4 @@ -import {MessageState, Metadata, MetadataEvent} from 'model'; +import {DeliveryState, Metadata, MetadataEvent} from 'model'; interface Event { type: 'message' | 'channel' | 'metadata'; @@ -7,7 +7,7 @@ interface Event { interface MessagePayload { id: string; content: string; - delivery_state: MessageState; + delivery_state: DeliveryState; from_contact: boolean; sent_at: Date; metadata: any; From 5642fad258c2c7fd2651baccec840815a00338de Mon Sep 17 00:00:00 2001 From: Paulo Diniz Date: Fri, 16 Apr 2021 11:20:58 +0200 Subject: [PATCH 24/50] =?UTF-8?q?[#1537]=20AWS=20Uninstall=20Docs=20-=20Re?= =?UTF-8?q?move=20reference=20to=E2=80=A6=20(#1564)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs/getting-started/installation/aws.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/docs/getting-started/installation/aws.md b/docs/docs/getting-started/installation/aws.md index 4ce8f46d19..c5a071f971 100644 --- a/docs/docs/getting-started/installation/aws.md +++ b/docs/docs/getting-started/installation/aws.md @@ -155,10 +155,11 @@ For more details please see our [Configuration Section](configuration.md). You can remove the Airy Core AWS installation by deleting the Airy Core AWS resources with the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html). -Retrieve the ID of the installation: +Retrieve the ID of the installation, in this case `my-airy` is the name of the installation that was passed on the creation process: ```sh -id=$(cat ~/.airy/cli.yaml | grep contextname | awk '{ print $2; }') +cd my-airy +id=$(cat cli.yaml | grep contextname | awk '{ print $2; }') echo ${id} ``` From c64d8d624dfc5e7de4d3c530c96dec63d86823d4 Mon Sep 17 00:00:00 2001 From: AudreyKj <38159391+AudreyKj@users.noreply.github.com> Date: Fri, 16 Apr 2021 11:47:47 +0200 Subject: [PATCH 25/50] fixed github variable (#1565) --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ec8c46854d..7a175e0394 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,6 +61,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + GITHUB_BRANCH: ${{ github.ref }} - name: Publish http-client library to npm if: ${{ startsWith(github.ref, 'refs/tags/npm-v') }} run: | From b9fa3fb5fcb9a7ecd40d8c70a5e996a286fa268f Mon Sep 17 00:00:00 2001 From: AudreyKj <38159391+AudreyKj@users.noreply.github.com> Date: Fri, 16 Apr 2021 15:55:41 +0200 Subject: [PATCH 26/50] fixed confikey chat plugin (#1571) --- frontend/ui/src/pages/Channels/MainPage/index.tsx | 2 +- integration/chat-plugin/end_conversation.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/ui/src/pages/Channels/MainPage/index.tsx b/frontend/ui/src/pages/Channels/MainPage/index.tsx index 94b30c8c6f..88cde9f892 100644 --- a/frontend/ui/src/pages/Channels/MainPage/index.tsx +++ b/frontend/ui/src/pages/Channels/MainPage/index.tsx @@ -61,7 +61,7 @@ const SourcesInfo: SourceInfo[] = [ image: , newChannelRoute: CHANNELS_CHAT_PLUGIN_ROUTE + '/new', channelsListRoute: CHANNELS_CONNECTED_ROUTE + '/chatplugin', - configKey: 'sources-chatplugin', + configKey: 'source-chat-plugin', channelsToShow: 4, itemInfoString: 'channels', dataCyAddChannelButton: cyChannelsChatPluginAddButton, diff --git a/integration/chat-plugin/end_conversation.spec.ts b/integration/chat-plugin/end_conversation.spec.ts index 288ef3c7ef..62667f3fbb 100644 --- a/integration/chat-plugin/end_conversation.spec.ts +++ b/integration/chat-plugin/end_conversation.spec.ts @@ -14,7 +14,7 @@ describe('End ChatPlugin Conversation', () => { cy.get(`[data-cy=${cyBubble}]`).click(); cy.get(`[data-cy=${cyInputbarTextarea}]`).type(Cypress.env('messageChatplugin')); cy.get(`[data-cy=${cyInputbarButton}]`).click(); - cy.get(`[data-cy=${cyChatPluginMessageList}]`).children().should('have.length', 2) + cy.get(`[data-cy=${cyChatPluginMessageList}]`).children().should('have.length', 2); cy.wait(500); @@ -26,6 +26,6 @@ describe('End ChatPlugin Conversation', () => { cy.get(`[data-cy=${cyInputbarButton}]`).click(); cy.wait(500); - cy.get(`[data-cy=${cyChatPluginMessageList}]`).children().should('have.length', 2) + cy.get(`[data-cy=${cyChatPluginMessageList}]`).children().should('have.length', 2); }); }); From ec8921a0655dd47ff676f5b01f69f1d1f22a6afb Mon Sep 17 00:00:00 2001 From: lucapette Date: Sat, 17 Apr 2021 11:02:10 +0200 Subject: [PATCH 27/50] [#1566] Add state endpoints (#1568) Fixes #1566 --- .../ConversationsController.java | 44 +++++++ .../ConversationSetStateRequestPayload.java | 18 +++ .../communication/ConversationsStateTest.java | 110 ++++++++++++++++++ .../co/airy/model/metadata/MetadataKeys.java | 1 + docs/docs/api/endpoints/conversations.md | 29 +++++ 5 files changed, 202 insertions(+) create mode 100644 backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java create mode 100644 backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsStateTest.java diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java index 0dd50f8e27..543aee6cb4 100644 --- a/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/ConversationsController.java @@ -12,6 +12,7 @@ import co.airy.core.api.communication.payload.ConversationListRequestPayload; import co.airy.core.api.communication.payload.ConversationListResponsePayload; import co.airy.core.api.communication.payload.ConversationResponsePayload; +import co.airy.core.api.communication.payload.ConversationSetStateRequestPayload; import co.airy.core.api.communication.payload.ConversationTagRequestPayload; import co.airy.core.api.communication.payload.PaginationData; import co.airy.model.metadata.MetadataKeys; @@ -39,6 +40,7 @@ import java.util.Set; import java.util.stream.Collectors; +import static co.airy.model.metadata.MetadataRepository.newConversationMetadata; import static co.airy.model.metadata.MetadataRepository.newConversationTag; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; @@ -230,4 +232,46 @@ ResponseEntity conversationUntag(@RequestBody @Valid ConversationTagRequestPa return ResponseEntity.noContent().build(); } + + @PostMapping("/conversations.setState") + ResponseEntity conversationSetState(@RequestBody @Valid ConversationSetStateRequestPayload requestPayload) { + final String conversationId = requestPayload.getConversationId().toString(); + final String state = requestPayload.getState(); + final ReadOnlyKeyValueStore store = stores.getConversationsStore(); + final Conversation conversation = store.get(conversationId); + + if (conversation == null) { + return ResponseEntity.notFound().build(); + } + + final Metadata metadata = newConversationMetadata(conversationId, MetadataKeys.ConversationKeys.STATE, state); + + try { + stores.storeMetadata(metadata); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new RequestErrorResponsePayload(e.getMessage())); + } + + return ResponseEntity.noContent().build(); + } + + @PostMapping("/conversations.removeState") + ResponseEntity conversationRemoveState(@RequestBody @Valid ConversationByIdRequestPayload requestPayload) { + final String conversationId = requestPayload.getConversationId().toString(); + final ReadOnlyKeyValueStore store = stores.getConversationsStore(); + final Conversation conversation = store.get(conversationId); + + if (conversation == null) { + return ResponseEntity.notFound().build(); + } + + try { + final Subject subject = new Subject("conversation", conversationId); + stores.deleteMetadata(subject, MetadataKeys.ConversationKeys.STATE); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new RequestErrorResponsePayload(e.getMessage())); + } + + return ResponseEntity.noContent().build(); + } } diff --git a/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java new file mode 100644 index 0000000000..646a374704 --- /dev/null +++ b/backend/api/communication/src/main/java/co/airy/core/api/communication/payload/ConversationSetStateRequestPayload.java @@ -0,0 +1,18 @@ +package co.airy.core.api.communication.payload; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; +import java.util.UUID; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ConversationSetStateRequestPayload { + @NotNull + private UUID conversationId; + @NotNull + private String state; +} diff --git a/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsStateTest.java b/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsStateTest.java new file mode 100644 index 0000000000..372c8c0972 --- /dev/null +++ b/backend/api/communication/src/test/java/co/airy/core/api/communication/ConversationsStateTest.java @@ -0,0 +1,110 @@ +package co.airy.core.api.communication; + +import co.airy.avro.communication.Channel; +import co.airy.avro.communication.ChannelConnectionState; +import co.airy.core.api.communication.util.TestConversation; +import co.airy.kafka.test.KafkaTestHelper; +import co.airy.kafka.test.junit.SharedKafkaTestResource; +import co.airy.spring.core.AirySpringBootApplication; +import co.airy.spring.test.WebTestHelper; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.UUID; + +import static co.airy.core.api.communication.util.Topics.applicationCommunicationChannels; +import static co.airy.core.api.communication.util.Topics.getTopics; +import static co.airy.test.Timing.retryOnException; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.core.Is.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) +@TestPropertySource(value = "classpath:test.properties") +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +class ConversationsStateTest { + @RegisterExtension + public static final SharedKafkaTestResource sharedKafkaTestResource = new SharedKafkaTestResource(); + + private static KafkaTestHelper kafkaTestHelper; + + @Autowired + private WebTestHelper webTestHelper; + + @BeforeAll + static void beforeAll() throws Exception { + kafkaTestHelper = new KafkaTestHelper(sharedKafkaTestResource, getTopics()); + kafkaTestHelper.beforeAll(); + } + + @AfterAll + static void afterAll() throws Exception { + kafkaTestHelper.afterAll(); + } + + @BeforeEach + void beforeEach() throws Exception { + webTestHelper.waitUntilHealthy(); + } + + @Test + void canSetandRemoveStateFromConversations() throws Exception { + final String userId = "user-id"; + final Channel channel = Channel.newBuilder() + .setConnectionState(ChannelConnectionState.CONNECTED) + .setId(UUID.randomUUID().toString()) + .setSource("facebook") + .setSourceChannelId("ps-id") + .build(); + + kafkaTestHelper.produceRecord(new ProducerRecord<>(applicationCommunicationChannels.name(), channel.getId(), channel)); + final String conversationId = UUID.randomUUID().toString(); + kafkaTestHelper.produceRecords(TestConversation.generateRecords(conversationId, channel, 1)); + + retryOnException(() -> webTestHelper.post("/conversations.info", + "{\"conversation_id\":\"" + conversationId + "\"}", userId) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(conversationId))), "conversation was not created"); + + final String state = "open"; + + webTestHelper.post("/conversations.setState", + "{\"conversation_id\":\"" + conversationId + "\",\"state\":\"" + state + "\"}", userId) + .andExpect(status().isNoContent()); + + retryOnException( + () -> webTestHelper.post("/conversations.info", + "{\"conversation_id\":\"" + conversationId + "\"}", userId) + .andExpect(status().isOk()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(conversationId))) + .andExpect(jsonPath("$.metadata.state", is(state))), + "conversation state was not set"); + + webTestHelper.post("/conversations.removeState", + "{\"conversation_id\":\"" + conversationId + "\"}", userId) + .andExpect(status().isNoContent()); + + retryOnException( + () -> webTestHelper.post("/conversations.info", + "{\"conversation_id\":\"" + conversationId + "\"}", userId) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is(conversationId))) + .andExpect(jsonPath("$.metadata.state").doesNotExist()), + "conversation state was not removed"); + } + +} diff --git a/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java b/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java index 00f89927fd..1bc2ebe9c2 100644 --- a/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java +++ b/backend/model/metadata/src/main/java/co/airy/model/metadata/MetadataKeys.java @@ -8,6 +8,7 @@ public class MetadataKeys { public static class ConversationKeys { public static final String TAGS = "tags"; public static final String UNREAD_COUNT = "unread_count"; + public static final String STATE = "state"; public static final String CONTACT = "contact"; diff --git a/docs/docs/api/endpoints/conversations.md b/docs/docs/api/endpoints/conversations.md index 38cc792476..eebfc6d56c 100644 --- a/docs/docs/api/endpoints/conversations.md +++ b/docs/docs/api/endpoints/conversations.md @@ -187,3 +187,32 @@ tag](/api/endpoints/tags.md#create). Returns status code `200` if successful. ``` **Empty response (204)** + +## Set the state of a conversation + +`POST /conversations.setState` + +**Sample request** + +```json5 +{ + "conversation_id": "CONVERSATION_ID", + "state": "open" +} +``` + +**Empty response (204)** + +## Remove the state of a conversation + +`POST /conversations.removeState` + +**Sample request** + +```json5 +{ + "conversation_id": "CONVERSATION_ID" +} +``` + +**Empty response (204)** From 0c9a6c687a96d4ffd57a5b7dfcd88498d9c947a7 Mon Sep 17 00:00:00 2001 From: AudreyKj <38159391+AudreyKj@users.noreply.github.com> Date: Mon, 19 Apr 2021 09:57:34 +0200 Subject: [PATCH 28/50] added conversation count in inbox (#1572) --- .../src/pages/Inbox/ConversationListHeader/index.tsx | 10 +++++++++- .../ui/src/pages/Inbox/ConversationsFilter/index.tsx | 12 +----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx b/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx index 2e188ac624..f72d313b81 100644 --- a/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx +++ b/frontend/ui/src/pages/Inbox/ConversationListHeader/index.tsx @@ -5,6 +5,7 @@ import {SearchField} from 'components'; import {StateModel} from '../../../reducers'; import {setSearch, resetFilteredConversationAction} from '../../../actions/conversationsFilter'; +import {allConversations} from '../../../selectors/conversations'; import {ReactComponent as IconSearch} from 'assets/images/icons/search.svg'; import {ReactComponent as BackIcon} from 'assets/images/icons/arrow-left-2.svg'; @@ -17,6 +18,7 @@ const mapStateToProps = (state: StateModel) => { return { user: state.data.user, currentFilter: state.data.conversations.filtered.currentFilter || {}, + conversations: allConversations(state), }; }; @@ -59,6 +61,12 @@ const ConversationListHeader = (props: ConnectedProps) => { handleSearch(value); }; + const InboxConversationCount = () => { + const {conversations} = props; + + return
{`Inbox (${conversations.length})`}
; + }; + const renderSearchInput = isShowingSearchInput ? (
) : (
-
Inbox
+
+
+ +
); }; diff --git a/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/EditChatPlugin.tsx b/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/EditChatPlugin.tsx index 3c3bef213a..b6ce8f9183 100644 --- a/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/EditChatPlugin.tsx +++ b/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/EditChatPlugin.tsx @@ -33,8 +33,8 @@ export const EditChatPlugin = ({channel, host, updateConnection}: EditChatPlugin }; const ConnectContent = () => { - const [displayName, setDisplayName] = useState(channel.metadata?.name || ''); - const [imageUrl, setImageUrl] = useState(channel.metadata?.imageUrl || ''); + const [displayName, setDisplayName] = useState(channel?.metadata?.name || ''); + const [imageUrl, setImageUrl] = useState(channel?.metadata?.imageUrl || ''); switch (currentPage) { case 'settings': From 9b7862c68584292c3c090f3577d3f5935a986dd1 Mon Sep 17 00:00:00 2001 From: Christoph Proeschel Date: Mon, 19 Apr 2021 14:59:29 +0200 Subject: [PATCH 34/50] [#1581] Prevent page from crashing when adding a channel (#1582) --- frontend/ui/src/pages/Channels/MainPage/index.module.scss | 1 - frontend/ui/src/pages/Channels/MainPage/index.tsx | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/ui/src/pages/Channels/MainPage/index.module.scss b/frontend/ui/src/pages/Channels/MainPage/index.module.scss index cec845d859..bb94f1da5a 100644 --- a/frontend/ui/src/pages/Channels/MainPage/index.module.scss +++ b/frontend/ui/src/pages/Channels/MainPage/index.module.scss @@ -5,7 +5,6 @@ @include font-base; display: flex; justify-content: space-between; - margin-bottom: 20px; color: var(--color-text-contrast); font-size: 39px; font-weight: 900; diff --git a/frontend/ui/src/pages/Channels/MainPage/index.tsx b/frontend/ui/src/pages/Channels/MainPage/index.tsx index 88cde9f892..b7c9d0ee7e 100644 --- a/frontend/ui/src/pages/Channels/MainPage/index.tsx +++ b/frontend/ui/src/pages/Channels/MainPage/index.tsx @@ -128,17 +128,14 @@ const MainPage = (props: MainPageProps & RouteComponentProps) => { const OpenRequirementsDialog = ({source}: {source: string}): JSX.Element => { switch (source) { case Source.facebook: - return setDisplayDialogFromSource('')} />; case Source.google: return setDisplayDialogFromSource('')} />; - break; - case Source.chatPlugin: - break; case Source.twilioSMS: - return setDisplayDialogFromSource('')} />; case Source.twilioWhatsapp: return setDisplayDialogFromSource('')} />; } + + return null; }; const channelsBySource = (Source: Source) => channels.filter((channel: Channel) => channel.source === Source); From 9dc02efa832b5902c09aa685c1972db5670a0a68 Mon Sep 17 00:00:00 2001 From: Ljupco Vangelski Date: Mon, 19 Apr 2021 16:19:42 +0200 Subject: [PATCH 35/50] [#740] Refactor config apply (#1544) --- .../src/main/resources/application.properties | 2 +- .../api/admin/WebhooksControllerTest.java | 2 +- .../src/main/resources/application.properties | 2 +- .../src/main/resources/application.properties | 2 +- .../src/main/resources/application.properties | 2 +- .../airy/core/chat_plugin/ChatController.java | 2 +- .../core/chat_plugin/config/AuthConfig.java | 4 +- .../src/main/resources/application.properties | 2 +- .../core/chat_plugin/ChatControllerTest.java | 2 +- .../src/test/resources/test.properties | 2 +- .../src/main/resources/application.properties | 2 +- .../src/main/resources/application.properties | 2 +- .../src/main/resources/application.properties | 2 +- cli/pkg/cmd/config/BUILD | 3 - cli/pkg/cmd/config/config.go | 47 ++++++-- cli/pkg/cmd/config/configmaps.go | 111 ------------------ cli/pkg/cmd/config/parser.go | 41 ++----- cli/pkg/kube/BUILD | 2 + cli/pkg/kube/configmaps.go | 37 ++++++ cli/pkg/workspace/template/airy.yaml | 2 +- .../installation/configuration.md | 27 +++-- infrastructure/controller/pkg/endpoints/BUILD | 1 - .../api-admin/templates/deployment.yaml | 3 + .../charts/api-auth/templates/deployment.yaml | 3 + .../templates/deployment.yaml | 3 + .../api-websocket/templates/deployment.yaml | 3 + .../charts/apps/charts/api/templates/api.yaml | 3 - .../apps/charts/api/templates/security.yaml | 9 ++ .../charts/frontend-chat-plugin/values.yaml | 2 +- .../frontend/charts/frontend-ui/values.yaml | 2 +- .../charts/webhook/templates/deployments.yaml | 10 +- .../templates/user-storage.yaml | 12 -- .../charts/apps/charts/media/Chart.yaml | 5 + .../charts/resolver}/Chart.yaml | 0 .../resolver}/templates/deployment.yaml | 30 ++++- .../charts/resolver}/templates/service.yaml | 0 .../charts/resolver}/values.yaml | 0 .../chatplugin/templates/deployment.yaml | 6 +- .../sources/charts/chatplugin/values.yaml | 2 +- .../facebook/templates/deployments.yaml | 16 +-- .../charts/google/templates/deployments.yaml | 16 +-- .../charts/twilio/templates/deployments.yaml | 16 +-- .../java/co/airy/spring/auth/AuthConfig.java | 4 +- .../auth/JwtAuthenticationFilterTest.java | 4 +- 44 files changed, 205 insertions(+), 243 deletions(-) delete mode 100644 cli/pkg/cmd/config/configmaps.go create mode 100644 cli/pkg/kube/configmaps.go create mode 100644 infrastructure/helm-chart/charts/apps/charts/api/templates/security.yaml delete mode 100644 infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/user-storage.yaml create mode 100644 infrastructure/helm-chart/charts/apps/charts/media/Chart.yaml rename infrastructure/helm-chart/charts/apps/charts/{media-resolver => media/charts/resolver}/Chart.yaml (100%) rename infrastructure/helm-chart/charts/apps/charts/{media-resolver => media/charts/resolver}/templates/deployment.yaml (71%) rename infrastructure/helm-chart/charts/apps/charts/{media-resolver => media/charts/resolver}/templates/service.yaml (100%) rename infrastructure/helm-chart/charts/apps/charts/{media-resolver => media/charts/resolver}/values.yaml (100%) diff --git a/backend/api/admin/src/main/resources/application.properties b/backend/api/admin/src/main/resources/application.properties index 444ce06a02..a146bae54b 100644 --- a/backend/api/admin/src/main/resources/application.properties +++ b/backend/api/admin/src/main/resources/application.properties @@ -2,5 +2,5 @@ kafka.brokers=${KAFKA_BROKERS} kafka.schema-registry-url=${KAFKA_SCHEMA_REGISTRY_URL} kafka.cleanup=${KAFKA_CLEANUP:false} kafka.commit-interval-ms=${KAFKA_COMMIT_INTERVAL_MS} -auth.jwt-secret=${JWT_SECRET} +auth.jwt-secret=${jwtSecret} kubernetes.namespace=${KUBERNETES_NAMESPACE} \ No newline at end of file diff --git a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java index 68a9213406..0826e91297 100644 --- a/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java +++ b/backend/api/admin/src/test/java/co/airy/core/api/admin/WebhooksControllerTest.java @@ -28,7 +28,7 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) @TestPropertySource(value = "classpath:test.properties", properties = { - "ALLOWED_ORIGINS=origin1,origin2", + "allowedOrigins=origin1,origin2", }) @AutoConfigureMockMvc @ExtendWith(SpringExtension.class) diff --git a/backend/api/auth/src/main/resources/application.properties b/backend/api/auth/src/main/resources/application.properties index dde95dc526..7cdbd9af75 100644 --- a/backend/api/auth/src/main/resources/application.properties +++ b/backend/api/auth/src/main/resources/application.properties @@ -4,7 +4,7 @@ spring.datasource.username=${DB_USERNAME} spring.datasource.password=${DB_PASSWORD} spring.flyway.enabled=true -auth.jwt-secret=${JWT_SECRET} +auth.jwt-secret=${jwtSecret} mail.host.url=${MAIL_URL} mail.host.port=${MAIL_PORT} diff --git a/backend/api/communication/src/main/resources/application.properties b/backend/api/communication/src/main/resources/application.properties index cba3b56051..1b7c23b19f 100644 --- a/backend/api/communication/src/main/resources/application.properties +++ b/backend/api/communication/src/main/resources/application.properties @@ -3,4 +3,4 @@ kafka.schema-registry-url=${KAFKA_SCHEMA_REGISTRY_URL} kafka.cleanup=${KAFKA_CLEANUP:false} kafka.commit-interval-ms=${KAFKA_COMMIT_INTERVAL_MS} -auth.jwt-secret=${JWT_SECRET} +auth.jwt-secret=${jwtSecret} diff --git a/backend/api/websocket/src/main/resources/application.properties b/backend/api/websocket/src/main/resources/application.properties index cba3b56051..1b7c23b19f 100644 --- a/backend/api/websocket/src/main/resources/application.properties +++ b/backend/api/websocket/src/main/resources/application.properties @@ -3,4 +3,4 @@ kafka.schema-registry-url=${KAFKA_SCHEMA_REGISTRY_URL} kafka.cleanup=${KAFKA_CLEANUP:false} kafka.commit-interval-ms=${KAFKA_COMMIT_INTERVAL_MS} -auth.jwt-secret=${JWT_SECRET} +auth.jwt-secret=${jwtSecret} diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java index 6351765b75..08045895c3 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/ChatController.java @@ -41,7 +41,7 @@ public class ChatController { private final Jwt jwt; private final String apiToken; - public ChatController(Stores stores, Jwt jwt, ObjectMapper objectMapper, @Value("${system_token:#{null}}") String apiToken) { + public ChatController(Stores stores, Jwt jwt, ObjectMapper objectMapper, @Value("${systemToken:#{null}}") String apiToken) { this.stores = stores; this.jwt = jwt; this.objectMapper = objectMapper; diff --git a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java index 1453207266..bf29f44e88 100644 --- a/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java +++ b/backend/sources/chat-plugin/src/main/java/co/airy/core/chat_plugin/config/AuthConfig.java @@ -26,7 +26,7 @@ public class AuthConfig extends WebSecurityConfigurerAdapter { private final Jwt jwt; private final String systemToken; - public AuthConfig(Jwt jwt, @Value("${system_token:#{null}}") String systemToken) { + public AuthConfig(Jwt jwt, @Value("${systemToken:#{null}}") String systemToken) { this.jwt = jwt; this.systemToken = systemToken; } @@ -46,7 +46,7 @@ protected void configure(final HttpSecurity http) throws Exception { @Bean CorsConfigurationSource corsConfigurationSource(final Environment environment) { - final String allowed = environment.getProperty("ALLOWED_ORIGINS", ""); + final String allowed = environment.getProperty("allowedOrigins", ""); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin(allowed); diff --git a/backend/sources/chat-plugin/src/main/resources/application.properties b/backend/sources/chat-plugin/src/main/resources/application.properties index a51befc8ca..095c989191 100644 --- a/backend/sources/chat-plugin/src/main/resources/application.properties +++ b/backend/sources/chat-plugin/src/main/resources/application.properties @@ -1,4 +1,4 @@ kafka.brokers=${KAFKA_BROKERS} kafka.schema-registry-url=${KAFKA_SCHEMA_REGISTRY_URL} kafka.commit-interval-ms=${KAFKA_COMMIT_INTERVAL_MS} -chat-plugin.auth.jwt-secret=${JWT_SECRET} +chat-plugin.auth.jwt-secret=${jwtSecret} diff --git a/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java b/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java index ee81b51575..9a00df87b5 100644 --- a/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java +++ b/backend/sources/chat-plugin/src/test/java/co/airy/core/chat_plugin/ChatControllerTest.java @@ -69,7 +69,7 @@ public class ChatControllerTest { @Value("${local.server.port}") private int port; - @Value("${system_token}") + @Value("${systemToken}") private String systemToken; private static KafkaTestHelper kafkaTestHelper; diff --git a/backend/sources/chat-plugin/src/test/resources/test.properties b/backend/sources/chat-plugin/src/test/resources/test.properties index 12ef627866..73d040ce86 100644 --- a/backend/sources/chat-plugin/src/test/resources/test.properties +++ b/backend/sources/chat-plugin/src/test/resources/test.properties @@ -1,4 +1,4 @@ kafka.cleanup=true kafka.commit-interval-ms=100 chat-plugin.auth.jwt-secret=this-needs-to-be-replaced-in-production-buffer:424242424242424242424242424242 -system_token=system-user-token +systemToken=system-user-token diff --git a/backend/sources/facebook/connector/src/main/resources/application.properties b/backend/sources/facebook/connector/src/main/resources/application.properties index fe8ecd4e38..58bc4666ad 100644 --- a/backend/sources/facebook/connector/src/main/resources/application.properties +++ b/backend/sources/facebook/connector/src/main/resources/application.properties @@ -1,4 +1,4 @@ -auth.jwt-secret=${JWT_SECRET} +auth.jwt-secret=${jwtSecret} facebook.webhook-secret=${FACEBOOK_WEBHOOK_SECRET} kafka.brokers=${KAFKA_BROKERS} kafka.cleanup=${KAFKA_CLEANUP:false} diff --git a/backend/sources/google/connector/src/main/resources/application.properties b/backend/sources/google/connector/src/main/resources/application.properties index c700759594..887baf50fa 100644 --- a/backend/sources/google/connector/src/main/resources/application.properties +++ b/backend/sources/google/connector/src/main/resources/application.properties @@ -1,4 +1,4 @@ -auth.jwt-secret=${JWT_SECRET} +auth.jwt-secret=${jwtSecret} google.auth.sa=${GOOGLE_SA_FILE} google.partner-key=${GOOGLE_PARTNER_KEY} kafka.brokers=${KAFKA_BROKERS} diff --git a/backend/sources/twilio/connector/src/main/resources/application.properties b/backend/sources/twilio/connector/src/main/resources/application.properties index a121db2adb..b859320ad4 100644 --- a/backend/sources/twilio/connector/src/main/resources/application.properties +++ b/backend/sources/twilio/connector/src/main/resources/application.properties @@ -1,4 +1,4 @@ -auth.jwt-secret=${JWT_SECRET} +auth.jwt-secret=${jwtSecret} kafka.brokers=${KAFKA_BROKERS} kafka.schema-registry-url=${KAFKA_SCHEMA_REGISTRY_URL} kafka.commit-interval-ms=${KAFKA_COMMIT_INTERVAL_MS} diff --git a/cli/pkg/cmd/config/BUILD b/cli/pkg/cmd/config/BUILD index a2fd4676ae..082b603f97 100644 --- a/cli/pkg/cmd/config/BUILD +++ b/cli/pkg/cmd/config/BUILD @@ -5,7 +5,6 @@ go_library( name = "config", srcs = [ "config.go", - "configmaps.go", "parser.go", ], importpath = "cli/pkg/cmd/config", @@ -16,9 +15,7 @@ go_library( "//cli/pkg/workspace", "@com_github_spf13_cobra//:cobra", "@in_gopkg_yaml_v2//:yaml_v2", - "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", - "@io_k8s_client_go//kubernetes:go_default_library", ], ) diff --git a/cli/pkg/cmd/config/config.go b/cli/pkg/cmd/config/config.go index b667f5c137..2b18eb71f4 100644 --- a/cli/pkg/cmd/config/config.go +++ b/cli/pkg/cmd/config/config.go @@ -4,8 +4,11 @@ import ( "cli/pkg/console" "cli/pkg/kube" "cli/pkg/workspace" + "context" "fmt" + "github.com/spf13/cobra" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var configFile string @@ -31,23 +34,45 @@ func applyConfig(cmd *cobra.Command, args []string) { kubeCtx := kube.Load() clientset, err := kubeCtx.GetClientSet() if err != nil { - console.Exit("Could not find an installation of Airy Core. Get started here https://airy.co/docs/core/getting-started/installation/introduction") - } - - if twilioApply(conf, clientset) { - fmt.Println("Twilio configuration applied.") + console.Exit("could not find an installation of Airy Core. Get started here https://airy.co/docs/core/getting-started/installation/introduction") } - if facebookApply(conf, clientset) { - fmt.Println("Facebook configuration applied.") + if len(conf.Security) != 0 { + applyErr := kube.ApplyConfigMap("security", conf.Kubernetes.Namespace, conf.Security, map[string]string{}, clientset) + if applyErr != nil { + fmt.Printf("unable to apply configuration for \"security\"\n Error:\n %v\n", applyErr) + } else { + fmt.Printf("applied configuration for \"security\"\n") + } } - if googleApply(conf, clientset) { - fmt.Println("Google configuration applied.") + configuredComponents := make(map[string]bool) + for componentType, _ := range conf.Components { + for componentName, componentValues := range conf.Components[componentType] { + configmapName := componentType + "-" + componentName + labels := map[string]string{ + "core.airy.co/component": configmapName, + } + applyErr := kube.ApplyConfigMap(configmapName, conf.Kubernetes.Namespace, componentValues, labels, clientset) + configuredComponents[configmapName] = true + if applyErr != nil { + fmt.Printf("unable to apply configuration for component: \"%s-%s\"\n Error:\n %v\n", componentType, componentName, applyErr) + } else { + fmt.Printf("applied configuration for component: \"%s-%s\"\n", componentType, componentName) + } + } } - if webhooksApply(conf, clientset) { - fmt.Println("Webhooks configuration applied.") + configmapList, _ := clientset.CoreV1().ConfigMaps(conf.Kubernetes.Namespace).List(context.TODO(), v1.ListOptions{LabelSelector: "core.airy.co/component"}) + for _, configmap := range configmapList.Items { + if !configuredComponents[configmap.ObjectMeta.Name] { + deleteErr := kube.DeleteConfigMap(configmap.ObjectMeta.Name, conf.Kubernetes.Namespace, clientset) + if deleteErr != nil { + fmt.Printf("unable to remove configuration for component %s.\n", configmap.ObjectMeta.Name) + } else { + fmt.Printf("removed configuration for component \"%s\".\n", configmap.ObjectMeta.Name) + } + } } } diff --git a/cli/pkg/cmd/config/configmaps.go b/cli/pkg/cmd/config/configmaps.go deleted file mode 100644 index c00fc3a1ad..0000000000 --- a/cli/pkg/cmd/config/configmaps.go +++ /dev/null @@ -1,111 +0,0 @@ -package config - -import ( - "context" - "fmt" - "os" - - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -func applyConfigMap(configMapName string, newCmData map[string]string, clientset *kubernetes.Clientset, namespace string) error { - cm, _ := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapName, v1.GetOptions{}) - - if cm.GetName() == "" { - _, err := clientset.CoreV1().ConfigMaps(namespace).Create(context.TODO(), - &corev1.ConfigMap{ - ObjectMeta: v1.ObjectMeta{ - Name: configMapName, - Namespace: namespace, - }, - Data: newCmData, - }, v1.CreateOptions{}) - return err - } else { - cm.Data = newCmData - _, err := clientset.CoreV1().ConfigMaps(namespace).Update(context.TODO(), cm, v1.UpdateOptions{}) - return err - } - -} - -func facebookApply(airyConf airyConf, clientset *kubernetes.Clientset) bool { - facebookConfig := airyConf.Apps.Sources.Facebook - if facebookConfig.AppID != "" || facebookConfig.AppSecret != "" || facebookConfig.WebhookSecret != "" { - configMapData := make(map[string]string, 0) - configMapData["FACEBOOK_APP_ID"] = facebookConfig.AppID - configMapData["FACEBOOK_APP_SECRET"] = facebookConfig.AppSecret - configMapData["FACEBOOK_WEBHOOK_SECRET"] = facebookConfig.WebhookSecret - err := applyConfigMap("sources-facebook", configMapData, clientset, airyConf.Global.Namespace) - - if err != nil { - fmt.Println("unable to update configMap: ", err) - os.Exit(1) - } - - return true - } - - return false -} - -func googleApply(airyConf airyConf, clientset *kubernetes.Clientset) bool { - googleConfig := airyConf.Apps.Sources.Google - if googleConfig.PartnerKey != "" || googleConfig.SaFile != "" { - configMapData := make(map[string]string, 0) - configMapData["GOOGLE_PARTNER_KEY"] = googleConfig.PartnerKey - configMapData["GOOGLE_SA_FILE"] = googleConfig.SaFile - - err := applyConfigMap("sources-google", configMapData, clientset, airyConf.Global.Namespace) - - if err != nil { - fmt.Println("unable to update configMap: ", err) - os.Exit(1) - } - - return true - } - - return false -} - -func twilioApply(airyConf airyConf, clientset *kubernetes.Clientset) bool { - twilioConfig := airyConf.Apps.Sources.Twilio - if twilioConfig.AccountSid != "" || twilioConfig.AuthToken != "" { - configMapData := make(map[string]string, 0) - configMapData["TWILIO_ACCOUNT_SID"] = twilioConfig.AccountSid - configMapData["TWILIO_AUTH_TOKEN"] = twilioConfig.AuthToken - - err := applyConfigMap("sources-twilio", configMapData, clientset, airyConf.Global.Namespace) - - if err != nil { - fmt.Println("unable to update configMap: ", err) - os.Exit(1) - } - - return true - } - - return false -} - -func webhooksApply(airyConf airyConf, clientset *kubernetes.Clientset,) bool { - webhooksConfig := airyConf.Apps.Webhooks - if webhooksConfig.Name != "" { - configMapData := make(map[string]string, 0) - configMapData["NAME"] = webhooksConfig.Name - - err := applyConfigMap("webhooks-config", configMapData, clientset, airyConf.Global.Namespace) - - if err != nil { - fmt.Println("unable to update configMap: ", err) - os.Exit(1) - } - - return true - } - - return false -} diff --git a/cli/pkg/cmd/config/parser.go b/cli/pkg/cmd/config/parser.go index 5ab41dbf8b..bc58b70b34 100644 --- a/cli/pkg/cmd/config/parser.go +++ b/cli/pkg/cmd/config/parser.go @@ -1,40 +1,24 @@ package config import ( - "gopkg.in/yaml.v2" "io/ioutil" + + "gopkg.in/yaml.v2" ) -type globalConf struct { +type kubernetesConf struct { AppImageTag string `yaml:"appImageTag"` ContainerRegistry string `yaml:"containerRegistry"` Namespace string `yaml:"namespace"` + NgrokEnabled string `yaml:"ngrokEnabled"` } -type appsConf struct { - Sources struct { - Twilio struct { - AuthToken string `yaml:"authToken"` - AccountSid string `yaml:"accountSid"` - } - Facebook struct { - AppID string `yaml:"appId"` - AppSecret string `yaml:"appSecret"` - WebhookSecret string `yaml:"webhookSecret"` - } - Google struct { - PartnerKey string `yaml:"partnerKey"` - SaFile string `yaml:"saFile"` - } - } - Webhooks struct { - Name string `yaml:"name"` - } -} +type componentsConf map[string]map[string]string type airyConf struct { - Global globalConf - Apps appsConf + Kubernetes kubernetesConf + Security map[string]string + Components map[string]componentsConf } func parseConf(configFile string) (airyConf, error) { @@ -42,14 +26,7 @@ func parseConf(configFile string) (airyConf, error) { if err != nil { return airyConf{}, err } - - conf := airyConf{ - Global: globalConf{ - Namespace: "default", - }, - } - + conf := airyConf{} err = yaml.Unmarshal(data, &conf) - return conf, err } diff --git a/cli/pkg/kube/BUILD b/cli/pkg/kube/BUILD index 3e45dd2693..23685fd4a5 100644 --- a/cli/pkg/kube/BUILD +++ b/cli/pkg/kube/BUILD @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "kube", srcs = [ + "configmaps.go", "context.go", "hosts.go", ], @@ -10,6 +11,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "@com_github_spf13_viper//:viper", + "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_client_go//kubernetes:go_default_library", "@io_k8s_client_go//tools/clientcmd:go_default_library", diff --git a/cli/pkg/kube/configmaps.go b/cli/pkg/kube/configmaps.go new file mode 100644 index 0000000000..461905e0f5 --- /dev/null +++ b/cli/pkg/kube/configmaps.go @@ -0,0 +1,37 @@ +package kube + +import ( + "context" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +func ApplyConfigMap(configmapName string, namespace string, cmData map[string]string, labels map[string]string, clientset *kubernetes.Clientset) error { + cm, _ := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configmapName, v1.GetOptions{}) + if cm.GetName() == "" { + _, err := clientset.CoreV1().ConfigMaps(namespace).Create(context.TODO(), + &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: configmapName, + Namespace: namespace, + Labels: labels, + }, + Data: cmData, + }, v1.CreateOptions{}) + return err + } else { + cm.Data = cmData + _, err := clientset.CoreV1().ConfigMaps(namespace).Update(context.TODO(), cm, v1.UpdateOptions{}) + return err + } +} + +func DeleteConfigMap(configmapName string, namespace string, clientset *kubernetes.Clientset) error { + cm, _ := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configmapName, v1.GetOptions{}) + if cm.GetName() != "" { + err := clientset.CoreV1().ConfigMaps(namespace).Delete(context.TODO(),configmapName, v1.DeleteOptions{}) + return err + } + return nil +} diff --git a/cli/pkg/workspace/template/airy.yaml b/cli/pkg/workspace/template/airy.yaml index 61c8e73d88..f2cbaf3fcf 100644 --- a/cli/pkg/workspace/template/airy.yaml +++ b/cli/pkg/workspace/template/airy.yaml @@ -1,4 +1,4 @@ -global: +kubernetes: containerRegistry: ghcr.io/airyhq namespace: default ngrokEnabled: false diff --git a/docs/docs/getting-started/installation/configuration.md b/docs/docs/getting-started/installation/configuration.md index 6b0a5d2dd8..a5d571d2e9 100644 --- a/docs/docs/getting-started/installation/configuration.md +++ b/docs/docs/getting-started/installation/configuration.md @@ -25,7 +25,7 @@ is enough not to provide any facebook specific configuration. Now let's have a look at the different sections so you can make the changes you are looking for. -### Global +### Kubernetes - `appImageTag` the image tag of the container images for the **Airy Components** @@ -61,13 +61,13 @@ cluster, PostgreSQL, and Redis. - `username` these credentials will be passed to the **API Auth Component** - `password` and they will be used to create the Postgres database -### Components +### Security -- `api` +- `jwtSecret` must be set to a long secure secret in production environments (default: random generated) +- `token` set to a long secure secret to use for machine [API authentication](api/authentication.md) (default: random generated) +- `allowedOrigins` your sites origin to prevent CORS-based attacks (default: "\*") - - `jwtSecret` must be set to a long secure secret in production environments - - `token` set to a long secure secret to use for machine [API authentication](api/authentication.md) - - `allowedOrigins` your sites origin to prevent CORS-based attacks +### Components - `sources` @@ -78,11 +78,16 @@ cluster, PostgreSQL, and Redis. The **Airy Controller** only starts configured sources. To keep system load to a minimum, only add the sources you are using. -- `webhooks` - - `name` -- `media-resolver` - - `storage` - - `s3` set these to your AWS S3 config to store source specific user data +- `integration` + - `webhook` + - `name` set this to the name of your webhook integration +- `media` + - `resolver` + - `s3Key` set this to your AWS S3 access key id + - `s3Secret` set this to your AWS S3 secret access key + - `s3Bucket` set this to your AWS S3 bucket + - `s3Region` set this to your AWS region + - `s3Path` set this to your AWS S3 path ### Tools diff --git a/infrastructure/controller/pkg/endpoints/BUILD b/infrastructure/controller/pkg/endpoints/BUILD index c5b3d49428..5a5a032318 100644 --- a/infrastructure/controller/pkg/endpoints/BUILD +++ b/infrastructure/controller/pkg/endpoints/BUILD @@ -7,7 +7,6 @@ go_library( importpath = "github.com/airyhq/airy/infrastructure/controller/pkg/endpoints", visibility = ["//visibility:public"], deps = [ - "@io_k8s_api//apps/v1:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_client_go//kubernetes:go_default_library", ], diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml index 8d02705a26..e3aa76bccc 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml @@ -31,6 +31,9 @@ spec: envFrom: - configMapRef: name: api-config + envFrom: + - configMapRef: + name: security env: - name: KUBERNETES_NAMESPACE valueFrom: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml index faca0f66ca..5647e39d19 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml @@ -31,6 +31,9 @@ spec: envFrom: - configMapRef: name: api-config + envFrom: + - configMapRef: + name: security env: - name: DB_USERNAME valueFrom: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml index 603ac0688b..371547208b 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml @@ -31,6 +31,9 @@ spec: envFrom: - configMapRef: name: api-config + envFrom: + - configMapRef: + name: security env: - name: KAFKA_BROKERS valueFrom: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml index 54f25cf56c..a126862aa9 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml @@ -31,6 +31,9 @@ spec: envFrom: - configMapRef: name: api-config + envFrom: + - configMapRef: + name: security env: - name: KAFKA_BROKERS valueFrom: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/templates/api.yaml b/infrastructure/helm-chart/charts/apps/charts/api/templates/api.yaml index 4eed517c56..3ab861e422 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/templates/api.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/templates/api.yaml @@ -9,6 +9,3 @@ data: MAIL_URL: {{ .Values.mailUrl }} MAIL_USERNAME: {{ .Values.mailUsername }} MAIL_PASSWORD: {{ .Values.mailPassword }} - JWT_SECRET: {{ .Values.jwtSecret | default (randAlphaNum 128) | quote }} - SYSTEM_TOKEN: {{ .Values.systemToken | default (randAlphaNum 24) | quote }} - ALLOWED_ORIGINS: {{ .Values.allowedOrigins | quote }} diff --git a/infrastructure/helm-chart/charts/apps/charts/api/templates/security.yaml b/infrastructure/helm-chart/charts/apps/charts/api/templates/security.yaml new file mode 100644 index 0000000000..f81ba5080f --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/api/templates/security.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: security + namespace: {{ .Values.global.namespace }} +data: + jwtSecret: {{ .Values.jwtSecret | default (randAlphaNum 128) | quote }} + systemToken: {{ .Values.systemToken | default (randAlphaNum 24) | quote }} + allowedOrigins: {{ .Values.allowedOrigins | quote }} diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml index 92f3ff1ecc..11fcdd5569 100644 --- a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-chat-plugin/values.yaml @@ -1,3 +1,3 @@ component: frontend-chat-plugin -mandatory: false +mandatory: true image: frontend/chat-plugin diff --git a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml index d95bb6cfc4..ac7f39953e 100644 --- a/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/frontend/charts/frontend-ui/values.yaml @@ -1,3 +1,3 @@ component: frontend-ui -mandatory: false +mandatory: true image: frontend/ui diff --git a/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml index 5a68f5be18..5d9bec820d 100644 --- a/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/integration/charts/webhook/templates/deployments.yaml @@ -44,8 +44,8 @@ spec: - name: WEBHOOK_NAME valueFrom: configMapKeyRef: - name: webhooks-config - key: NAME + name: "{{ .Values.component }}" + key: name livenessProbe: httpGet: path: /health @@ -90,8 +90,6 @@ metadata: core.airy.co/managed: "true" core.airy.co/mandatory: "{{ .Values.mandatory }}" core.airy.co/component: "{{ .Values.component }}" - annotations: - core.airy.co/config-items-optional: "WEBHOOK_NAME" spec: replicas: 0 selector: @@ -142,8 +140,8 @@ spec: - name: WEBHOOK_NAME valueFrom: configMapKeyRef: - name: webhooks-config - key: NAME + name: "{{ .Values.component }}" + key: name livenessProbe: tcpSocket: port: 6000 diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/user-storage.yaml b/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/user-storage.yaml deleted file mode 100644 index 10c2174377..0000000000 --- a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/user-storage.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{ if .Values.storage }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: user-storage -data: - STORAGE_S3_KEY: {{ .Values.storage.s3.key }} - STORAGE_S3_SECRET: "{{ .Values.storage.s3.secret }}" - STORAGE_S3_BUCKET: {{ .Values.storage.s3.bucket }} - STORAGE_S3_REGION: {{ .Values.storage.s3.region }} - STORAGE_S3_PATH: {{ .Values.storage.s3.path }} -{{ end }} diff --git a/infrastructure/helm-chart/charts/apps/charts/media/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/media/Chart.yaml new file mode 100644 index 0000000000..a4a914e3b8 --- /dev/null +++ b/infrastructure/helm-chart/charts/apps/charts/media/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for the Airy Core media components +name: media +version: 1.0 diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/Chart.yaml b/infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/Chart.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/media-resolver/Chart.yaml rename to infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/Chart.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/templates/deployment.yaml similarity index 71% rename from infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml rename to infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/templates/deployment.yaml index e09048fee0..e705fc780c 100644 --- a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/templates/deployment.yaml @@ -28,9 +28,6 @@ spec: - name: app image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always - envFrom: - - configMapRef: - name: user-storage env: - name: KAFKA_BROKERS valueFrom: @@ -48,7 +45,32 @@ spec: name: kafka-config key: KAFKA_COMMIT_INTERVAL_MS - name: SERVICE_NAME - value: media-resolver + value: "{{ .Values.component }}" + - name: STORAGE_S3_KEY + valueFrom: + configMapKeyRef: + name: "{{ .Values.component }}" + key: s3Key + - name: STORAGE_S3_SECRET + valueFrom: + configMapKeyRef: + name: "{{ .Values.component }}" + key: s3Secret + - name: STORAGE_S3_BUCKET + valueFrom: + configMapKeyRef: + name: "{{ .Values.component }}" + key: s3Bucket + - name: STORAGE_S3_REGION + valueFrom: + configMapKeyRef: + name: "{{ .Values.component }}" + key: s3Region + - name: STORAGE_S3_PATH + valueFrom: + configMapKeyRef: + name: "{{ .Values.component }}" + key: s3Path livenessProbe: httpGet: path: /actuator/health diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/service.yaml b/infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/templates/service.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/media-resolver/templates/service.yaml rename to infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/templates/service.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml b/infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/values.yaml similarity index 100% rename from infrastructure/helm-chart/charts/apps/charts/media-resolver/values.yaml rename to infrastructure/helm-chart/charts/apps/charts/media/charts/resolver/values.yaml diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml index 931db96d94..2c92ba6d22 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml @@ -29,11 +29,11 @@ spec: image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always env: - - name: ALLOWED_ORIGINS + - name: allowedOrigins valueFrom: configMapKeyRef: - name: api-config - key: ALLOWED_ORIGINS + name: security + key: allowedOrigins - name: JWT_SECRET valueFrom: configMapKeyRef: diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml index 649fa29dff..789b8a057e 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml @@ -1,3 +1,3 @@ component: source-chat-plugin -mandatory: false +mandatory: true image: sources/chat-plugin \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml index e1df570c9c..2d2ee7f89e 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml @@ -52,18 +52,18 @@ spec: - name: FACEBOOK_WEBHOOK_SECRET valueFrom: configMapKeyRef: - name: sources-facebook - key: FACEBOOK_WEBHOOK_SECRET + name: "{{ .Values.component }}" + key: WebhookSecret - name: FACEBOOK_APP_ID valueFrom: configMapKeyRef: - name: sources-facebook - key: FACEBOOK_APP_ID + name: "{{ .Values.component }}" + key: appId - name: FACEBOOK_APP_SECRET valueFrom: configMapKeyRef: - name: sources-facebook - key: FACEBOOK_APP_SECRET + name: "{{ .Values.component }}" + key: appSecret livenessProbe: httpGet: path: /actuator/health @@ -144,8 +144,8 @@ spec: - name: FACEBOOK_APP_ID valueFrom: configMapKeyRef: - name: sources-facebook - key: FACEBOOK_APP_ID + name: "{{ .Values.component }}" + key: appId - name: SERVICE_NAME value: facebook-events-router livenessProbe: diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml index 9b3697b133..d4e37780ab 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml @@ -50,13 +50,13 @@ spec: - name: GOOGLE_SA_FILE valueFrom: configMapKeyRef: - name: sources-google - key: GOOGLE_SA_FILE + name: "{{ .Values.component }}" + key: saFile - name: GOOGLE_PARTNER_KEY valueFrom: configMapKeyRef: - name: sources-google - key: GOOGLE_PARTNER_KEY + name: "{{ .Values.component }}" + key: partnerKey livenessProbe: httpGet: path: /actuator/health @@ -137,13 +137,13 @@ spec: - name: GOOGLE_SA_FILE valueFrom: configMapKeyRef: - name: sources-google - key: GOOGLE_SA_FILE + name: "{{ .Values.component }}" + key: saFile - name: GOOGLE_PARTNER_KEY valueFrom: configMapKeyRef: - name: sources-google - key: GOOGLE_PARTNER_KEY + name: "{{ .Values.component }}" + key: partnerKey livenessProbe: tcpSocket: port: 6000 diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml index 03a7e67449..7315010f33 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml @@ -50,13 +50,13 @@ spec: - name: TWILIO_AUTH_TOKEN valueFrom: configMapKeyRef: - name: sources-twilio - key: TWILIO_AUTH_TOKEN + name: "{{ .Values.component }}" + key: authToken - name: TWILIO_ACCOUNT_SID valueFrom: configMapKeyRef: - name: sources-twilio - key: TWILIO_ACCOUNT_SID + name: "{{ .Values.component }}" + key: accountSid livenessProbe: httpGet: path: /actuator/health @@ -139,13 +139,13 @@ spec: - name: TWILIO_AUTH_TOKEN valueFrom: configMapKeyRef: - name: sources-twilio - key: TWILIO_AUTH_TOKEN + name: "{{ .Values.component }}" + key: authToken - name: TWILIO_ACCOUNT_SID valueFrom: configMapKeyRef: - name: sources-twilio - key: TWILIO_ACCOUNT_SID + name: "{{ .Values.component }}" + key: accountSid livenessProbe: tcpSocket: port: 6000 diff --git a/lib/java/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java index e77d813c9f..7fb0542bf4 100644 --- a/lib/java/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java +++ b/lib/java/spring/auth/src/main/java/co/airy/spring/auth/AuthConfig.java @@ -28,7 +28,7 @@ public class AuthConfig extends WebSecurityConfigurerAdapter { private final String[] ignoreAuthPatterns; private final String systemToken; - public AuthConfig(Jwt jwt, @Value("${system_token:#{null}}") String systemToken, List ignorePatternBeans) { + public AuthConfig(Jwt jwt, @Value("${systemToken:#{null}}") String systemToken, List ignorePatternBeans) { this.jwt = jwt; this.systemToken = systemToken; this.ignoreAuthPatterns = ignorePatternBeans.stream() @@ -54,7 +54,7 @@ protected void configure(final HttpSecurity http) throws Exception { @Bean CorsConfigurationSource corsConfigurationSource(final Environment environment) { - final String allowed = environment.getProperty("ALLOWED_ORIGINS", ""); + final String allowed = environment.getProperty("allowedOrigins", ""); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin(allowed); diff --git a/lib/java/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java b/lib/java/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java index a42f7433c8..1f430f97e2 100644 --- a/lib/java/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java +++ b/lib/java/spring/auth/src/test/java/co/airy/spring/auth/JwtAuthenticationFilterTest.java @@ -21,8 +21,8 @@ @SpringBootTest(properties = { "auth.jwt-secret=424242424242424242424242424242424242424242424242424242", - "system_token=user-generated-api-token", - "ALLOWED_ORIGINS=*" + "systemToken=user-generated-api-token", + "allowedOrigins=*" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AirySpringBootApplication.class) @AutoConfigureMockMvc @ExtendWith(SpringExtension.class) From 4b2f6e36af7cc0e198aeff0c855cc2818a8beb1b Mon Sep 17 00:00:00 2001 From: Ljupco Vangelski Date: Mon, 19 Apr 2021 18:10:02 +0200 Subject: [PATCH 36/50] [#740] Fix env variables (#1583) --- .../apps/charts/api/charts/api-admin/templates/deployment.yaml | 1 - .../apps/charts/api/charts/api-auth/templates/deployment.yaml | 1 - .../api/charts/api-communication/templates/deployment.yaml | 1 - .../charts/api/charts/api-websocket/templates/deployment.yaml | 1 - .../charts/sources/charts/chatplugin/templates/deployment.yaml | 3 +++ 5 files changed, 3 insertions(+), 4 deletions(-) diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml index e3aa76bccc..20bc37ab64 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml @@ -31,7 +31,6 @@ spec: envFrom: - configMapRef: name: api-config - envFrom: - configMapRef: name: security env: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml index 5647e39d19..946fb566aa 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-auth/templates/deployment.yaml @@ -31,7 +31,6 @@ spec: envFrom: - configMapRef: name: api-config - envFrom: - configMapRef: name: security env: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml index 371547208b..5f4d24fc9b 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml @@ -31,7 +31,6 @@ spec: envFrom: - configMapRef: name: api-config - envFrom: - configMapRef: name: security env: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml index a126862aa9..0ff486796a 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml @@ -31,7 +31,6 @@ spec: envFrom: - configMapRef: name: api-config - envFrom: - configMapRef: name: security env: diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml index 2c92ba6d22..9b7e5378b2 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/templates/deployment.yaml @@ -28,6 +28,9 @@ spec: - name: app image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always + envFrom: + - configMapRef: + name: security env: - name: allowedOrigins valueFrom: From abc0595aebeda23560924509fa79c0a642c8d321 Mon Sep 17 00:00:00 2001 From: Thorsten Date: Tue, 20 Apr 2021 09:33:06 +0200 Subject: [PATCH 37/50] [#1524] Added conversationState to conversationList (#1560) --- .../ui/src/actions/conversations/index.ts | 12 +++++ .../ConversationListItem/index.module.scss | 51 +++++++++++++++++++ .../Inbox/ConversationListItem/index.tsx | 33 +++++++++++- .../src/reducers/data/conversations/index.ts | 17 +++++++ lib/typescript/httpclient/client.ts | 8 +++ .../httpclient/src/endpoints/index.ts | 2 + .../src/endpoints/metadataUpsert.ts | 10 ++++ .../src/endpoints/setStateConversation.ts | 9 ++++ .../payload/MetadataUpsertRequestPayload.ts | 5 ++ .../SetStateConversationRequestPayload.ts | 4 ++ .../httpclient/src/payload/index.ts | 2 + lib/typescript/model/Conversation.ts | 1 + 12 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 lib/typescript/httpclient/src/endpoints/metadataUpsert.ts create mode 100644 lib/typescript/httpclient/src/endpoints/setStateConversation.ts create mode 100644 lib/typescript/httpclient/src/payload/MetadataUpsertRequestPayload.ts create mode 100644 lib/typescript/httpclient/src/payload/SetStateConversationRequestPayload.ts diff --git a/frontend/ui/src/actions/conversations/index.ts b/frontend/ui/src/actions/conversations/index.ts index f5e899fd6b..6ef760da13 100644 --- a/frontend/ui/src/actions/conversations/index.ts +++ b/frontend/ui/src/actions/conversations/index.ts @@ -13,6 +13,7 @@ const CONVERSATION_ADD_ERROR = '@@conversations/ADD_ERROR_TO_CONVERSATION'; const CONVERSATION_REMOVE_ERROR = '@@conversations/REMOVE_ERROR_FROM_CONVERSATION'; const CONVERSATION_REMOVE_TAG = '@@conversations/CONVERSATION_REMOVE_TAG'; const CONVERSATION_UPDATE_PAGINATION_DATA = '@@conversation/UPDATE_PAGINATION_DATA'; +const CONVERSATION_SET_STATE = '@@conversations/CONVERSATION_SET_STATE'; export const loadingConversationAction = createAction( CONVERSATION_LOADING, @@ -48,6 +49,11 @@ export const updateMessagesPaginationDataAction = createAction( (conversationId: string, paginationData: Pagination) => ({conversationId, paginationData}) )<{conversationId: string; paginationData: Pagination}>(); +export const setStateConversationAction = createAction( + CONVERSATION_SET_STATE, + (conversationId: string, state: string) => ({conversationId, state}) +)<{conversationId: string; state: string}>(); + export const listConversations = () => async (dispatch: Dispatch) => { dispatch(loadingConversationsAction()); return HttpClientInstance.listConversations({page_size: 10}).then((response: PaginatedResponse) => { @@ -99,6 +105,12 @@ export const readConversations = (conversationId: string) => (dispatch: Dispatch ); }; +export const conversationState = (conversationId: string, state: string) => (dispatch: Dispatch) => { + HttpClientInstance.setStateConversation({conversationId, state}).then(() => + dispatch(setStateConversationAction(conversationId, state)) + ); +}; + export const addTagToConversation = (conversationId: string, tagId: string) => (dispatch: Dispatch) => { HttpClientInstance.tagConversation({conversationId, tagId}).then(() => dispatch( diff --git a/frontend/ui/src/pages/Inbox/ConversationListItem/index.module.scss b/frontend/ui/src/pages/Inbox/ConversationListItem/index.module.scss index fabfa031f0..a0533bc932 100644 --- a/frontend/ui/src/pages/Inbox/ConversationListItem/index.module.scss +++ b/frontend/ui/src/pages/Inbox/ConversationListItem/index.module.scss @@ -90,6 +90,57 @@ font-weight: 700; } +.closedStateButton { + width: 24px; + height: 24px; + background: none; + padding: 0px; + + svg { + margin-top: 1px; + path { + fill: var(--color-soft-green); + } + } + + button { + cursor: pointer; + width: 24px; + height: 24px; + background: none; + border: none; + padding: 0px; + outline: none; + + &:hover { + svg { + margin-top: 1px; + path { + fill: var(--color-soft-green); + } + } + } + } +} + +.openStateButton { + width: 20px; + height: 20px; + background: none; + border: 1px solid var(--color-red-alert); + margin: 2px 2px 0 0; + border-radius: 50%; + + button { + cursor: pointer; + width: 24px; + height: 24px; + background: none; + border: none; + outline: none; + } +} + .contactLastMessage { @include font-base; color: var(--color-text-gray); diff --git a/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx b/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx index 98b39c233b..6c162a8677 100644 --- a/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx +++ b/frontend/ui/src/pages/Inbox/ConversationListItem/index.tsx @@ -10,9 +10,10 @@ import {formatTimeOfMessage} from '../../../services/format/date'; import {Message} from 'model'; import {MergedConversation} from '../../../reducers'; import {INBOX_CONVERSATIONS_ROUTE} from '../../../routes/routes'; -import {readConversations} from '../../../actions/conversations'; +import {readConversations, conversationState} from '../../../actions/conversations'; import styles from './index.module.scss'; +import {ReactComponent as Checkmark} from 'assets/images/icons/checkmark-circle.svg'; interface FormattedMessageProps { message: Message; @@ -26,6 +27,7 @@ type ConversationListItemProps = { const mapDispatchToProps = { readConversations, + conversationState, }; const connector = connect(null, mapDispatchToProps); @@ -38,10 +40,36 @@ const FormattedMessage = ({message}: FormattedMessageProps) => { }; const ConversationListItem = (props: ConversationListItemProps) => { - const {conversation, active, style, readConversations} = props; + const {conversation, active, style, readConversations, conversationState} = props; const participant = conversation.metadata.contact; const unread = conversation.metadata.unreadCount > 0; + const currentConversationState = conversation.metadata.state; + + const eventHandler = (event: React.MouseEvent) => { + const newState = currentConversationState === 'OPEN' ? 'CLOSED' : 'OPEN'; + conversationState(conversation.id, newState); + event.preventDefault(); + event.stopPropagation(); + }; + + const OpenStateButton = () => { + return ( +
+
+ ); + }; + + const ClosedStateButton = () => { + return ( +
+ +
+ ); + }; useEffect(() => { if (active && unread) { @@ -64,6 +92,7 @@ const ConversationListItem = (props: ConversationListItemProps) => {
{participant && participant.displayName}
+ {currentConversationState === 'OPEN' ? : }
diff --git a/frontend/ui/src/reducers/data/conversations/index.ts b/frontend/ui/src/reducers/data/conversations/index.ts index 85ac7412c9..2af6c3f3e5 100644 --- a/frontend/ui/src/reducers/data/conversations/index.ts +++ b/frontend/ui/src/reducers/data/conversations/index.ts @@ -193,6 +193,22 @@ function allReducer( action: Action | MessageAction ): AllConversationsState { switch (action.type) { + case getType(actions.setStateConversationAction): + return { + ...state, + items: { + ...state.items, + [action.payload.conversationId]: { + id: action.payload.conversationId, + ...state.items[action.payload.conversationId], + metadata: { + ...state.items[action.payload.conversationId].metadata, + state: action.payload.state, + }, + }, + }, + }; + case getType(metadataActions.setMetadataAction): if (action.payload.subject !== 'conversation') { return state; @@ -208,6 +224,7 @@ function allReducer( metadata: { // Ensure that there is always a display name present ...(action.payload as MetadataEvent).metadata, + state: action.payload.metadata.state || state.items[action.payload.identifier].metadata.state, contact: { ...state.items[action.payload.identifier]?.metadata.contact, ...(action.payload as MetadataEvent).metadata.contact, diff --git a/lib/typescript/httpclient/client.ts b/lib/typescript/httpclient/client.ts index 2d789c3a94..66d68877ac 100644 --- a/lib/typescript/httpclient/client.ts +++ b/lib/typescript/httpclient/client.ts @@ -16,6 +16,8 @@ import { UpdateChannelRequestPayload, ListTemplatesRequestPayload, PaginatedResponse, + MetadataUpsertRequestPayload, + SetStateConversationRequestPayload, } from './src/payload'; import { listChannelsDef, @@ -40,6 +42,8 @@ import { sendMessagesDef, getConfigDef, listTemplatesDef, + metadataUpsertDef, + setStateConversationDef, } from './src/endpoints'; function isString(object: any) { @@ -175,6 +179,10 @@ export class HttpClient { public listTemplates = this.getRequest(listTemplatesDef); + public metadataUpsert = this.getRequest(metadataUpsertDef); + + public setStateConversation = this.getRequest(setStateConversationDef); + private getRequest({ endpoint, mapRequest, diff --git a/lib/typescript/httpclient/src/endpoints/index.ts b/lib/typescript/httpclient/src/endpoints/index.ts index 495d444b0e..5f7c07ad5f 100644 --- a/lib/typescript/httpclient/src/endpoints/index.ts +++ b/lib/typescript/httpclient/src/endpoints/index.ts @@ -20,3 +20,5 @@ export * from './sendMessages'; export * from './getConfig'; export * from './updateChannel'; export * from './listTemplates'; +export * from './metadataUpsert'; +export * from './setStateConversation'; diff --git a/lib/typescript/httpclient/src/endpoints/metadataUpsert.ts b/lib/typescript/httpclient/src/endpoints/metadataUpsert.ts new file mode 100644 index 0000000000..225303a803 --- /dev/null +++ b/lib/typescript/httpclient/src/endpoints/metadataUpsert.ts @@ -0,0 +1,10 @@ +import {MetadataUpsertRequestPayload} from '../payload/MetadataUpsertRequestPayload'; + +export const metadataUpsertDef = { + endpoint: 'metadata.upsert', + mapRequest: (request: MetadataUpsertRequestPayload) => ({ + id: request.id, + subject: request.subject, + data: request.data, + }), +}; diff --git a/lib/typescript/httpclient/src/endpoints/setStateConversation.ts b/lib/typescript/httpclient/src/endpoints/setStateConversation.ts new file mode 100644 index 0000000000..c603e4a78a --- /dev/null +++ b/lib/typescript/httpclient/src/endpoints/setStateConversation.ts @@ -0,0 +1,9 @@ +import {SetStateConversationRequestPayload} from '../payload/SetStateConversationRequestPayload'; + +export const setStateConversationDef = { + endpoint: 'conversations.setState', + mapRequest: (request: SetStateConversationRequestPayload) => ({ + conversation_id: request.conversationId, + state: request.state, + }), +}; diff --git a/lib/typescript/httpclient/src/payload/MetadataUpsertRequestPayload.ts b/lib/typescript/httpclient/src/payload/MetadataUpsertRequestPayload.ts new file mode 100644 index 0000000000..d9ccd04965 --- /dev/null +++ b/lib/typescript/httpclient/src/payload/MetadataUpsertRequestPayload.ts @@ -0,0 +1,5 @@ +export interface MetadataUpsertRequestPayload { + id: string; + subject: string; + data: {}; +} diff --git a/lib/typescript/httpclient/src/payload/SetStateConversationRequestPayload.ts b/lib/typescript/httpclient/src/payload/SetStateConversationRequestPayload.ts new file mode 100644 index 0000000000..fedfd2e9f2 --- /dev/null +++ b/lib/typescript/httpclient/src/payload/SetStateConversationRequestPayload.ts @@ -0,0 +1,4 @@ +export interface SetStateConversationRequestPayload { + conversationId: string; + state: string; +} diff --git a/lib/typescript/httpclient/src/payload/index.ts b/lib/typescript/httpclient/src/payload/index.ts index cad569f27f..93e8eb21dc 100644 --- a/lib/typescript/httpclient/src/payload/index.ts +++ b/lib/typescript/httpclient/src/payload/index.ts @@ -15,3 +15,5 @@ export * from './SendMessagesRequestPayload'; export * from './TagConversationRequestPayload'; export * from './UntagConversationRequestPayload'; export * from './UpdateChannelRequestPayload'; +export * from './MetadataUpsertRequestPayload'; +export * from './SetStateConversationRequestPayload'; diff --git a/lib/typescript/model/Conversation.ts b/lib/typescript/model/Conversation.ts index 86aef3ec40..8f7d1f52bf 100644 --- a/lib/typescript/model/Conversation.ts +++ b/lib/typescript/model/Conversation.ts @@ -9,6 +9,7 @@ export type ConversationMetadata = Metadata & { tags: { [tagId: string]: string; }; + state: string; }; export interface Conversation { From 3f0144e3429a511728ed22bea3781a1f8a1774e1 Mon Sep 17 00:00:00 2001 From: Christoph Proeschel Date: Tue, 20 Apr 2021 13:26:14 +0200 Subject: [PATCH 38/50] [#1590] Fix api host variable injection in chatplugin (#1591) --- frontend/chat-plugin/index.ts | 1 + frontend/chat-plugin/src/App.tsx | 32 +++++++++++-------- .../src/airyRenderProps/AiryBubble/index.tsx | 2 +- .../airyRenderProps/AiryHeaderBar/index.tsx | 2 +- .../airyRenderProps/AiryInputBar/index.tsx | 2 +- frontend/chat-plugin/src/api/index.tsx | 16 ++++------ .../chat-plugin/src/components/chat/index.tsx | 2 +- frontend/chat-plugin/src/config.ts | 17 ++++++++-- frontend/chat-plugin/src/defaultScript.tsx | 3 +- frontend/chat-plugin/src/websocket/index.ts | 19 +++-------- .../ChatPlugin/sections/CustomiseSection.tsx | 26 ++++----------- 11 files changed, 57 insertions(+), 65 deletions(-) diff --git a/frontend/chat-plugin/index.ts b/frontend/chat-plugin/index.ts index 6089c3781c..44ca2a2613 100644 --- a/frontend/chat-plugin/index.ts +++ b/frontend/chat-plugin/index.ts @@ -1 +1,2 @@ export * from './src/AiryChatPlugin'; +export * from './src/config'; diff --git a/frontend/chat-plugin/src/App.tsx b/frontend/chat-plugin/src/App.tsx index 94fabd4879..b88ea86e79 100644 --- a/frontend/chat-plugin/src/App.tsx +++ b/frontend/chat-plugin/src/App.tsx @@ -1,6 +1,16 @@ import React, {Component} from 'react'; import Chat from './components/chat'; import style from './App.module.scss'; +import {Config} from './config'; + +declare global { + interface Window { + airy: { + host: string; + channelId: string; + }; + } +} export default class App extends Component { render() { @@ -19,10 +29,18 @@ export default class App extends Component { }), }; + const apiHost: string = window.airy ? window.airy.host : process.env.API_HOST; + return (
{channelId ? ( - + ) : ( Widget authorization failed. Please check your installation. )} @@ -31,18 +49,6 @@ export default class App extends Component { } } -export type Config = { - welcomeMessage?: {}; - headerText?: string; - headerTextColor?: string; - backgroundColor?: string; - primaryColor?: string; - accentColor?: string; - bubbleIcon?: string; - sendMessageIcon?: string; - showMode: boolean; -}; - export const config: Config = { welcomeMessage: { fallback: 'Hello!\n\nWelcome to Airy!', diff --git a/frontend/chat-plugin/src/airyRenderProps/AiryBubble/index.tsx b/frontend/chat-plugin/src/airyRenderProps/AiryBubble/index.tsx index c50ec79c61..748aadf333 100644 --- a/frontend/chat-plugin/src/airyRenderProps/AiryBubble/index.tsx +++ b/frontend/chat-plugin/src/airyRenderProps/AiryBubble/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Config} from '../../App'; +import {Config} from '../../config'; import style from './index.module.scss'; type Props = { diff --git a/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx b/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx index f2f9f707b3..0b2d2b187e 100644 --- a/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx +++ b/frontend/chat-plugin/src/airyRenderProps/AiryHeaderBar/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Config} from '../../App'; +import {Config} from '../../config'; import style from './index.module.scss'; import {ReactComponent as CloseButton} from 'assets/images/icons/close.svg'; import {ReactComponent as MinimizeButton} from 'assets/images/icons/minimize-button.svg'; diff --git a/frontend/chat-plugin/src/airyRenderProps/AiryInputBar/index.tsx b/frontend/chat-plugin/src/airyRenderProps/AiryInputBar/index.tsx index e222dd336f..869982540f 100644 --- a/frontend/chat-plugin/src/airyRenderProps/AiryInputBar/index.tsx +++ b/frontend/chat-plugin/src/airyRenderProps/AiryInputBar/index.tsx @@ -1,7 +1,7 @@ import React, {ChangeEvent, FormEvent, KeyboardEvent, createRef, useEffect} from 'react'; import style from './index.module.scss'; import {cyInputbarTextarea, cyInputbarButton} from 'chat-plugin-handles'; -import {Config} from '../../App'; +import {Config} from '../../config'; type AiryInputBarProps = { sendMessage: (text: string) => void; diff --git a/frontend/chat-plugin/src/api/index.tsx b/frontend/chat-plugin/src/api/index.tsx index 4a559442f4..2880a91aed 100644 --- a/frontend/chat-plugin/src/api/index.tsx +++ b/frontend/chat-plugin/src/api/index.tsx @@ -1,17 +1,13 @@ import {QuickReplyCommand, SuggestionResponse, TextContent} from 'render/providers/chatplugin/chatPluginModel'; import {setResumeTokenInStorage} from '../storage'; -declare const window: { - airy: { - host: string; - channelId: string; - }; +let host; +export const setApiHost = apiHost => { + host = apiHost; }; -const API_HOST = window.airy ? window.airy.host : process.env.API_HOST; - export const sendMessage = (message: TextContent | SuggestionResponse | QuickReplyCommand, token: string) => { - return fetch(`${API_HOST}/chatplugin.send`, { + return fetch(`${host}/chatplugin.send`, { method: 'POST', body: JSON.stringify(convertToBody(message)), headers: { @@ -39,7 +35,7 @@ const convertToBody = (message: TextContent | SuggestionResponse | QuickReplyCom }; export const getResumeToken = async (channelId: string, authToken: string) => { - const resumeChat = await fetch(`${API_HOST}/chatplugin.resumeToken`, { + const resumeChat = await fetch(`${host}/chatplugin.resumeToken`, { method: 'POST', body: JSON.stringify({}), headers: { @@ -53,7 +49,7 @@ export const getResumeToken = async (channelId: string, authToken: string) => { export const start = async (channelId: string, resumeToken: string) => { try { - const response = await fetch(`${API_HOST}/chatplugin.authenticate`, { + const response = await fetch(`${host}/chatplugin.authenticate`, { method: 'POST', body: JSON.stringify({ channel_id: channelId, diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index 10b73d0597..da12e93e4f 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -67,7 +67,7 @@ const Chat = (props: Props) => { useEffect(() => { if (config.showMode) return; - ws = new WebSocket(props.channelId, onReceive, setInitialMessages, (state: ConnectionState) => { + ws = new WebSocket(props.apiHost, props.channelId, onReceive, setInitialMessages, (state: ConnectionState) => { setConnectionState(state); }); ws.start().catch(error => { diff --git a/frontend/chat-plugin/src/config.ts b/frontend/chat-plugin/src/config.ts index 783ab7dcd8..535eb97c0f 100644 --- a/frontend/chat-plugin/src/config.ts +++ b/frontend/chat-plugin/src/config.ts @@ -1,18 +1,29 @@ -import {Config} from './App'; - export type RenderCtrl = { toggleHideChat: () => void; }; export type RenderProp = (ctrl?: RenderCtrl) => JSX.Element; +export type Config = { + welcomeMessage?: {}; + headerText?: string; + headerTextColor?: string; + backgroundColor?: string; + primaryColor?: string; + accentColor?: string; + bubbleIcon?: string; + sendMessageIcon?: string; + showMode: boolean; +}; + export type AuthConfiguration = { channelId: string; resumeToken?: string; - config?: Config; }; export type AiryChatPluginConfiguration = AuthConfiguration & { + apiHost: string; + config?: Config; headerBarProp?: RenderProp; inputBarProp?: RenderProp; airyMessageProp?: RenderProp; diff --git a/frontend/chat-plugin/src/defaultScript.tsx b/frontend/chat-plugin/src/defaultScript.tsx index 9f19b5c626..fc6118b6d7 100644 --- a/frontend/chat-plugin/src/defaultScript.tsx +++ b/frontend/chat-plugin/src/defaultScript.tsx @@ -1,5 +1,5 @@ import AiryWidget from './AiryWidget'; -import {Config} from './App'; +import {Config} from './config'; const body = document.getElementsByTagName('body')[0]; @@ -35,6 +35,7 @@ declare const window: { if (window.airy.channelId) { new AiryWidget({ + apiHost: window.airy.host, channelId: window.airy.channelId, resumeToken: window.airy.resumeToken, config: window.airy.config, diff --git a/frontend/chat-plugin/src/websocket/index.ts b/frontend/chat-plugin/src/websocket/index.ts index 0b8caccbc3..09d3e97ed9 100644 --- a/frontend/chat-plugin/src/websocket/index.ts +++ b/frontend/chat-plugin/src/websocket/index.ts @@ -8,17 +8,6 @@ import {getResumeTokenFromStorage, resetStorage} from '../storage'; /* eslint-disable @typescript-eslint/no-var-requires */ const camelcaseKeys = require('camelcase-keys'); -declare global { - interface Window { - airy: { - host: string; - channelId: string; - }; - } -} - -const API_HOST = window.airy ? window.airy.host : process.env.API_HOST; -const host = new URL(API_HOST).host; // https: -> wss: and http: -> ws: const protocol = location.protocol.replace('http', 'ws'); @@ -29,6 +18,7 @@ export enum ConnectionState { class WebSocket { client: Client; + apiHost: string; channelId: string; token: string; setInitialMessages: (messages: Array) => void; @@ -38,11 +28,13 @@ class WebSocket { updateConnectionState: (state: ConnectionState) => void; constructor( + apiHost: string, channelId: string, onReceive: messageCallbackType, setInitialMessages: (messages: Array) => void, updateConnectionState: (state: ConnectionState) => void ) { + this.apiHost = new URL(apiHost).host; this.channelId = channelId; this.onReceive = onReceive; this.setInitialMessages = setInitialMessages; @@ -54,13 +46,10 @@ class WebSocket { this.token = token; this.client = new Client({ - brokerURL: `${protocol}//${host}/ws.chatplugin`, + brokerURL: `${protocol}//${this.apiHost}/ws.chatplugin`, connectHeaders: { Authorization: `Bearer ${token}`, }, - debug: function (str) { - console.info(str); - }, reconnectDelay: 0, heartbeatIncoming: 4000, heartbeatOutgoing: 4000, diff --git a/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/CustomiseSection.tsx b/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/CustomiseSection.tsx index 5159d112b0..55ce6f75a7 100644 --- a/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/CustomiseSection.tsx +++ b/frontend/ui/src/pages/Channels/Providers/Airy/ChatPlugin/sections/CustomiseSection.tsx @@ -2,27 +2,14 @@ import React, {createRef, useState} from 'react'; import {Button, Input, ListenOutsideClick} from 'components'; import styles from './CustomiseSection.module.scss'; import {SketchPicker} from 'react-color'; -import {AiryChatPlugin} from 'chat-plugin'; +import {AiryChatPlugin, AiryChatPluginConfiguration} from 'chat-plugin'; +import {env} from '../../../../../../env'; interface CustomiseSectionProps { channelId: string; host: string; } -interface DemoConfig { - config: { - headerText?: string; - headerTextColor?: string; - primaryColor?: string; - backgroundColor?: string; - accentColor?: string; - bubbleIcon?: string; - sendMessageIcon?: string; - showMode: boolean; - }; - channelId: string; -} - export const CustomiseSection = ({channelId, host}: CustomiseSectionProps) => { const [headerText, setHeaderText] = useState(''); const [bubbleIconUrl, setBubbleIconUrl] = useState(''); @@ -54,7 +41,7 @@ export const CustomiseSection = ({channelId, host}: CustomiseSectionProps) => { setShowBackgroundColorPicker(!showBackgroundColorPicker); }; - const getConfig = () => { + const getTemplateConfig = () => { if ( headerText === '' && bubbleIconUrl === '' && @@ -80,7 +67,9 @@ export const CustomiseSection = ({channelId, host}: CustomiseSectionProps) => { };`; }; - const demoConfig: DemoConfig = { + const demoConfig: AiryChatPluginConfiguration = { + apiHost: env.API_HOST, + channelId, config: { showMode: true, ...(headerText && {headerText}), @@ -91,7 +80,6 @@ export const CustomiseSection = ({channelId, host}: CustomiseSectionProps) => { ...(bubbleIconUrl && {bubbleIcon: bubbleIconUrl}), ...(sendMessageIconUrl && {sendMessageIcon: sendMessageIconUrl}), }, - channelId, }; const copyToClipboard = () => { @@ -104,7 +92,7 @@ export const CustomiseSection = ({channelId, host}: CustomiseSectionProps) => { (function(w, d, s, n) { w[n] = w[n] || {}; w[n].channelId = "${channelId}"; - w[n].host = "${host}";${getConfig()} + w[n].host = "${host}";${getTemplateConfig()} var f = d.getElementsByTagName(s)[0], j = d.createElement(s); j.async = true; From 33ec1a7a2102f9d9cde4abcff810709498c20d49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:33:16 +0200 Subject: [PATCH 39/50] Bump core-js from 3.10.1 to 3.10.2 (#1584) Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.10.1 to 3.10.2. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.10.2/packages/core-js) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 34369b4c49..ea6f183844 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2632,9 +2632,9 @@ core-js-compat@^3.9.0, core-js-compat@^3.9.1: semver "7.0.0" core-js@3: - version "3.10.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.10.1.tgz#e683963978b6806dcc6c0a4a8bd4ab0bdaf3f21a" - integrity sha512-pwCxEXnj27XG47mu7SXAwhLP3L5CrlvCB91ANUkIz40P27kUcvNfSdvyZJ9CLHiVoKSp+TTChMQMSKQEH/IQxA== + version "3.10.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.10.2.tgz#17cb038ce084522a717d873b63f2b3ee532e2cd5" + integrity sha512-W+2oVYeNghuBr3yTzZFQ5rfmjZtYB/Ubg87R5YOmlGrIb+Uw9f7qjUbhsj+/EkXhcV7eOD3jiM4+sgraX3FZUw== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" From 0082a1c6c5b1f8c575f74ca8f86d384e12543779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:33:29 +0200 Subject: [PATCH 40/50] Bump sass from 1.32.10 to 1.32.11 (#1585) Bumps [sass](https://github.com/sass/dart-sass) from 1.32.10 to 1.32.11. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.32.10...1.32.11) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 04998b3e5a..5f63b31ca8 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "minimist": "^1.2.5", "prettier": "^2.2.1", "react-hot-loader": "^4.13.0", - "sass": "^1.32.10", + "sass": "^1.32.11", "sass-loader": "^11", "style-loader": "^2.0.0", "terser-webpack-plugin": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index ea6f183844..ffa1c6405f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6477,10 +6477,10 @@ sass-loader@^11: klona "^2.0.4" neo-async "^2.6.2" -sass@^1.32.10: - version "1.32.10" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.10.tgz#d40da4e20031b450359ee1c7e69bc8cc89569241" - integrity sha512-Nx0pcWoonAkn7CRp0aE/hket1UP97GiR1IFw3kcjV3pnenhWgZEWUf0ZcfPOV2fK52fnOcK3JdC/YYZ9E47DTQ== +sass@^1.32.11: + version "1.32.11" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.11.tgz#b236b3ea55c76602c2ef2bd0445f0db581baa218" + integrity sha512-O9tRcob/fegUVSIV1ihLLZcftIOh0AF1VpKgusUfLqnb2jQ0GLDwI5ivv1FYWivGv8eZ/AwntTyTzjcHu0c/qw== dependencies: chokidar ">=3.0.0 <4.0.0" From 2100a438d76bd3a12afb3cabfdaeaecda265b2b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:33:41 +0200 Subject: [PATCH 41/50] Bump webpack from 5.33.2 to 5.34.0 (#1586) Bumps [webpack](https://github.com/webpack/webpack) from 5.33.2 to 5.34.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.33.2...v5.34.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 5f63b31ca8..e2931b7ed5 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "terser-webpack-plugin": "^5.1.1", "typescript": "3.7.4", "url-loader": "^4.1.1", - "webpack": "^5.33.2", + "webpack": "^5.34.0", "webpack-bundle-analyzer": "^4.4.1", "webpack-cli": "^4.6.0", "webpack-dev-server": "^3.11.2" diff --git a/yarn.lock b/yarn.lock index ffa1c6405f..cb3254c156 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1286,10 +1286,10 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.46": - version "0.0.46" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" - integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== +"@types/estree@*", "@types/estree@^0.0.47": + version "0.0.47" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" + integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== "@types/glob@^7.1.1": version "7.1.3" @@ -3089,10 +3089,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" - integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== +enhanced-resolve@^5.8.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.0.tgz#d9deae58f9d3773b6a111a5a46831da5be5c9ac0" + integrity sha512-Sl3KRpJA8OpprrtaIswVki3cWPiPKxXuFxJXBp+zNb6s6VwNWwFRUdtmzd2ReUut8n+sCPx7QCtQ7w5wfJhSgQ== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -7689,20 +7689,20 @@ webpack-sources@^2.1.1: source-list-map "^2.0.1" source-map "^0.6.1" -webpack@^5.33.2: - version "5.33.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.33.2.tgz#c049717c9b038febf5a72fd2f53319ad59a8c1fc" - integrity sha512-X4b7F1sYBmJx8mlh2B7mV5szEkE0jYNJ2y3akgAP0ERi0vLCG1VvdsIxt8lFd4st6SUy0lf7W0CCQS566MBpJg== +webpack@^5.34.0: + version "5.34.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.34.0.tgz#8f12bfd3474e7543232345b89294cfe8a5191c10" + integrity sha512-+WiFMgaZqhu7zKN64LQ7z0Ml4WWI+9RwG6zmS0wJDQXiCeg3hpN8fYFNJ+6WlosDT55yVxTfK7XHUAOVR4rLyA== dependencies: "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.46" + "@types/estree" "^0.0.47" "@webassemblyjs/ast" "1.11.0" "@webassemblyjs/wasm-edit" "1.11.0" "@webassemblyjs/wasm-parser" "1.11.0" acorn "^8.0.4" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.7.0" + enhanced-resolve "^5.8.0" es-module-lexer "^0.4.0" eslint-scope "^5.1.1" events "^3.2.0" From b22784cea0a1e9b29229a4b189cd82ffda0f21c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:37:44 +0200 Subject: [PATCH 42/50] Bump css-loader from 5.2.2 to 5.2.4 (#1587) Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 5.2.2 to 5.2.4. - [Release notes](https://github.com/webpack-contrib/css-loader/releases) - [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/css-loader/compare/v5.2.2...v5.2.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e2931b7ed5..426a4c32f0 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@typescript-eslint/parser": "^4.22.0", "babel-loader": "^8.0.6", "copy-webpack-plugin": "^8.1.1", - "css-loader": "^5.2.2", + "css-loader": "^5.2.4", "cypress": "^7.1.0", "eslint": "^7.24.0", "eslint-plugin-react": "^7.23.2", diff --git a/yarn.lock b/yarn.lock index cb3254c156..7a4d4a8787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2672,10 +2672,10 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-loader@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.2.tgz#65f2c1482255f15847ecad6cbc515cae8a5b234e" - integrity sha512-IS722y7Lh2Yq+acMR74tdf3faMOLRP2RfLwS0VzSS7T98IHtacMWJLku3A0OBTFHB07zAa4nWBhA8gfxwQVWGQ== +css-loader@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.4.tgz#e985dcbce339812cb6104ef3670f08f9893a1536" + integrity sha512-OFYGyINCKkdQsTrSYxzGSFnGS4gNjcXkKkQgWxK138jgnPt+lepxdjSZNc8sHAl5vP3DhsJUxufWIjOwI8PMMw== dependencies: camelcase "^6.2.0" icss-utils "^5.1.0" From 131afe30ae7d213245c795c79ee74d5784bf9148 Mon Sep 17 00:00:00 2001 From: Pascal Holy Date: Tue, 20 Apr 2021 14:38:28 +0200 Subject: [PATCH 43/50] Fixes #1594 --- VERSION | 2 +- docs/docs/changelog.md | 62 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2063416324..66333910a4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.18.0-alpha +0.18.0 diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index 387f111b31..71c5453c1d 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -3,6 +3,68 @@ title: Changelog sidebar_label: πŸ“ Changelog --- +## 0.18.0 + +#### πŸš€ Features + +- [[#1524](https://github.com/airyhq/airy/issues/1524)] Added conversationState to conversationList [[#1560](https://github.com/airyhq/airy/pull/1560)] +- [[#1515](https://github.com/airyhq/airy/issues/1515)] Create airy chat plugin library + use it in UI [[#1550](https://github.com/airyhq/airy/pull/1550)] +- [[#1577](https://github.com/airyhq/airy/issues/1577)] Conversations.setState Returns 404 [[#1578](https://github.com/airyhq/airy/pull/1578)] +- [[#1526](https://github.com/airyhq/airy/issues/1526)] Added conversation count in inbox [[#1572](https://github.com/airyhq/airy/pull/1572)] +- [[#1566](https://github.com/airyhq/airy/issues/1566)] Add state endpoints [[#1568](https://github.com/airyhq/airy/pull/1568)] +- [[#1537](https://github.com/airyhq/airy/issues/1537)] AWS Uninstall Docs - Remove reference to… [[#1564](https://github.com/airyhq/airy/pull/1564)] +- [[#1502](https://github.com/airyhq/airy/issues/1502)] Improve model lib [[#1547](https://github.com/airyhq/airy/pull/1547)] +- [[#740](https://github.com/airyhq/airy/issues/740)] Uses components endpoint on service discovery [[#1549](https://github.com/airyhq/airy/pull/1549)] +- [[#1503](https://github.com/airyhq/airy/issues/1503)] Cypress test to end a conversation in chatplugin [[#1543](https://github.com/airyhq/airy/pull/1543)] +- [[#740](https://github.com/airyhq/airy/issues/740)] Adding k8s endpoint to airy controller [[#1546](https://github.com/airyhq/airy/pull/1546)] +- [[#740](https://github.com/airyhq/airy/issues/740)] Label and introspect components [[#1510](https://github.com/airyhq/airy/pull/1510)] +- [[#740](https://github.com/airyhq/airy/issues/740)] Refactor config apply [[#1544](https://github.com/airyhq/airy/pull/1544)] + +#### πŸ› Bug Fixes + +- [[#1590](https://github.com/airyhq/airy/issues/1590)] Fix api host variable injection in chatplugin [[#1591](https://github.com/airyhq/airy/pull/1591)] +- [[#740](https://github.com/airyhq/airy/issues/740)] Fix env variables [[#1583](https://github.com/airyhq/airy/pull/1583)] +- [[#1581](https://github.com/airyhq/airy/issues/1581)] Prevent page from crashing when adding a channel [[#1582](https://github.com/airyhq/airy/pull/1582)] +- [[#1570](https://github.com/airyhq/airy/issues/1570)] Fixed confikey chat plugin [[#1571](https://github.com/airyhq/airy/pull/1571)] +- [[#1565](https://github.com/airyhq/airy/issues/1565)] Fixed github variable [[#1565](https://github.com/airyhq/airy/pull/1565)] +- [[#635](https://github.com/airyhq/airy/issues/635)] Fix deployment of the library to npm [[#1411](https://github.com/airyhq/airy/pull/1411)] +- [[#1555](https://github.com/airyhq/airy/issues/1555)] Fixed template button [[#1556](https://github.com/airyhq/airy/pull/1556)] +- [[#1540](https://github.com/airyhq/airy/issues/1540)] Added return to messageBubble [[#1542](https://github.com/airyhq/airy/pull/1542)] +- [[#1535](https://github.com/airyhq/airy/issues/1535)] Release version uses correct app image tag [[#1538](https://github.com/airyhq/airy/pull/1538)] + +#### πŸ“š Documentation + +- [[#1399](https://github.com/airyhq/airy/issues/1399)] Add Rasa suggested reply guide [[#1548](https://github.com/airyhq/airy/pull/1548)] +- [[#1532](https://github.com/airyhq/airy/issues/1532)] Remove step 4 of airy cli installation docs [[#1534](https://github.com/airyhq/airy/pull/1534)] + +#### 🧰 Maintenance + +- Bump css-loader from 5.2.2 to 5.2.4 [[#1587](https://github.com/airyhq/airy/pull/1587)] +- Bump webpack from 5.33.2 to 5.34.0 [[#1586](https://github.com/airyhq/airy/pull/1586)] +- Bump sass from 1.32.10 to 1.32.11 [[#1585](https://github.com/airyhq/airy/pull/1585)] +- Bump core-js from 3.10.1 to 3.10.2 [[#1584](https://github.com/airyhq/airy/pull/1584)] +- Bump @bazel/typescript from 3.3.0 to 3.4.0 [[#1552](https://github.com/airyhq/airy/pull/1552)] +- Bump css-loader from 5.2.1 to 5.2.2 [[#1574](https://github.com/airyhq/airy/pull/1574)] +- Bump sass from 1.32.8 to 1.32.10 [[#1573](https://github.com/airyhq/airy/pull/1573)] +- Bump @types/node from 14.14.40 to 14.14.41 [[#1561](https://github.com/airyhq/airy/pull/1561)] +- Bump @types/node from 14.14.39 to 14.14.40 [[#1559](https://github.com/airyhq/airy/pull/1559)] +- Bump react-markdown from 5.0.3 to 6.0.0 [[#1554](https://github.com/airyhq/airy/pull/1554)] +- Bump @types/node from 14.14.37 to 14.14.39 [[#1553](https://github.com/airyhq/airy/pull/1553)] +- Bump webpack from 5.32.0 to 5.33.2 [[#1551](https://github.com/airyhq/airy/pull/1551)] +- Bump react-modal from 3.12.1 to 3.13.1 [[#1545](https://github.com/airyhq/airy/pull/1545)] +- Bump @typescript-eslint/parser from 4.21.0 to 4.22.0 [[#1528](https://github.com/airyhq/airy/pull/1528)] +- Bump cypress from 7.0.1 to 7.1.0 [[#1529](https://github.com/airyhq/airy/pull/1529)] +- Bump @typescript-eslint/eslint-plugin from 4.21.0 to 4.22.0 [[#1530](https://github.com/airyhq/airy/pull/1530)] +- Bump webpack from 5.31.2 to 5.32.0 [[#1527](https://github.com/airyhq/airy/pull/1527)] + +#### Airy CLI + +You can download the Airy CLI for your operating system from the following links: + +[MacOS](https://airy-core-binaries.s3.amazonaws.com/0.18.0/darwin/amd64/airy) +[Linux](https://airy-core-binaries.s3.amazonaws.com/0.18.0/linux/amd64/airy) +[Windows](https://airy-core-binaries.s3.amazonaws.com/0.18.0/windows/amd64/airy.exe) + ## 0.17.0 #### πŸš€ Features From 94bc227b437ce693acc8a7f8502052a4aca69fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Pr=C3=B6schel?= Date: Tue, 20 Apr 2021 16:30:42 +0200 Subject: [PATCH 44/50] hotfix chatplugin api host --- frontend/chat-plugin/src/AiryChatPlugin.tsx | 1 + frontend/chat-plugin/src/components/chat/index.tsx | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/chat-plugin/src/AiryChatPlugin.tsx b/frontend/chat-plugin/src/AiryChatPlugin.tsx index c3b597c31b..82c70d9448 100644 --- a/frontend/chat-plugin/src/AiryChatPlugin.tsx +++ b/frontend/chat-plugin/src/AiryChatPlugin.tsx @@ -1,5 +1,6 @@ import React, {useEffect, createRef, CSSProperties} from 'react'; import {AiryChatPluginConfiguration} from './config'; +import {setApiHost} from './api'; import AiryWidget from './AiryWidget'; import styles from './AiryChatPlugin.module.scss'; diff --git a/frontend/chat-plugin/src/components/chat/index.tsx b/frontend/chat-plugin/src/components/chat/index.tsx index da12e93e4f..5ab67c02d0 100644 --- a/frontend/chat-plugin/src/components/chat/index.tsx +++ b/frontend/chat-plugin/src/components/chat/index.tsx @@ -26,7 +26,7 @@ import {cyBubble, cyChatPluginMessageList, cyChatPluginEndChatModalButton} from import {getResumeTokenFromStorage, resetStorage} from '../../storage'; import {ModalDialogue} from '../../components/modal'; import NewConversation from '../../components/newConversation'; -import {start} from '../../api'; +import {setApiHost, start} from '../../api'; import style from './index.module.scss'; @@ -66,6 +66,7 @@ const Chat = (props: Props) => { useEffect(() => { if (config.showMode) return; + setApiHost(props.apiHost); ws = new WebSocket(props.apiHost, props.channelId, onReceive, setInitialMessages, (state: ConnectionState) => { setConnectionState(state); @@ -74,7 +75,7 @@ const Chat = (props: Props) => { console.error(error); setInstallError(error.message); }); - }, []); + }, [props.apiHost, props.channelId]); useEffect(() => { setAnimation(''); From 85da3ee8bf1f99391cf6136a6b1cef8df9eb1c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Pr=C3=B6schel?= Date: Tue, 20 Apr 2021 16:48:28 +0200 Subject: [PATCH 45/50] fix lint --- frontend/chat-plugin/src/AiryChatPlugin.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/chat-plugin/src/AiryChatPlugin.tsx b/frontend/chat-plugin/src/AiryChatPlugin.tsx index 82c70d9448..c3b597c31b 100644 --- a/frontend/chat-plugin/src/AiryChatPlugin.tsx +++ b/frontend/chat-plugin/src/AiryChatPlugin.tsx @@ -1,6 +1,5 @@ import React, {useEffect, createRef, CSSProperties} from 'react'; import {AiryChatPluginConfiguration} from './config'; -import {setApiHost} from './api'; import AiryWidget from './AiryWidget'; import styles from './AiryChatPlugin.module.scss'; From 17b44fe35947cefbd30393efef9eb2dc8a1356f2 Mon Sep 17 00:00:00 2001 From: ljupcovangelski Date: Tue, 20 Apr 2021 16:51:19 +0200 Subject: [PATCH 46/50] [#1594] Fix names of the source components --- .../charts/apps/charts/sources/charts/chatplugin/values.yaml | 2 +- .../charts/apps/charts/sources/charts/facebook/values.yaml | 2 +- .../charts/apps/charts/sources/charts/google/values.yaml | 2 +- .../charts/apps/charts/sources/charts/twilio/values.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml index 789b8a057e..7158ddd96e 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/chatplugin/values.yaml @@ -1,3 +1,3 @@ -component: source-chat-plugin +component: sources-chat-plugin mandatory: true image: sources/chat-plugin \ No newline at end of file diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml index 57160ff90f..22743c6e6e 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/values.yaml @@ -1,4 +1,4 @@ -component: source-facebook +component: sources-facebook mandatory: false imageConnector: sources/facebook-connector imageEventsRouter: sources/facebook-events-router diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml index a9f55b95e8..de3f251098 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/values.yaml @@ -1,4 +1,4 @@ -component: source-google +component: sources-google mandatory: false imageConnector: sources/google-connector imageEventsRouter: sources/google-events-router diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml index ac5e929b36..236fef954e 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/values.yaml @@ -1,4 +1,4 @@ -component: source-twilio +component: sources-twilio mandatory: false imageConnector: sources/twilio-connector imageEventsRouter: sources/twilio-events-router From 7b2673e32659e742ee07c577473b7e38d451bb5b Mon Sep 17 00:00:00 2001 From: ljupcovangelski Date: Tue, 20 Apr 2021 17:22:16 +0200 Subject: [PATCH 47/50] [#1594] Increase timeout for helm install --- cli/pkg/cmd/create/helm.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/pkg/cmd/create/helm.go b/cli/pkg/cmd/create/helm.go index 80f1beb792..1196e5a0b3 100644 --- a/cli/pkg/cmd/create/helm.go +++ b/cli/pkg/cmd/create/helm.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io/ioutil" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -79,6 +80,7 @@ func (h *Helm) InstallCharts(overrides []string) error { "--values", "/apps/config/airy-config-map.yaml", "--set", "global.appImageTag=" + h.version, "--set", "global.namespace=" + h.namespace, + "--timeout", "10m0s", "core", "/apps/helm-chart/"}, overrides...)) } From 3eb9a05d6c57c7f05af4da48665425a5c4fb9745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Pr=C3=B6schel?= Date: Tue, 20 Apr 2021 17:26:13 +0200 Subject: [PATCH 48/50] Fix component name --- frontend/ui/src/pages/Channels/MainPage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ui/src/pages/Channels/MainPage/index.tsx b/frontend/ui/src/pages/Channels/MainPage/index.tsx index b7c9d0ee7e..5436c2270f 100644 --- a/frontend/ui/src/pages/Channels/MainPage/index.tsx +++ b/frontend/ui/src/pages/Channels/MainPage/index.tsx @@ -61,7 +61,7 @@ const SourcesInfo: SourceInfo[] = [ image: , newChannelRoute: CHANNELS_CHAT_PLUGIN_ROUTE + '/new', channelsListRoute: CHANNELS_CONNECTED_ROUTE + '/chatplugin', - configKey: 'source-chat-plugin', + configKey: 'sources-chat-plugin', channelsToShow: 4, itemInfoString: 'channels', dataCyAddChannelButton: cyChannelsChatPluginAddButton, From 5f4746cd779fe8f7c42cba4b9cbebacfa8a6c85f Mon Sep 17 00:00:00 2001 From: ljupcovangelski Date: Wed, 21 Apr 2021 10:20:02 +0200 Subject: [PATCH 49/50] [#1594] Fix configmap for sources --- .../charts/sources/charts/facebook/templates/deployments.yaml | 2 +- .../charts/sources/charts/google/templates/deployments.yaml | 2 +- .../charts/sources/charts/twilio/templates/deployments.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml index 2d2ee7f89e..454e1f2b13 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/facebook/templates/deployments.yaml @@ -30,7 +30,7 @@ spec: imagePullPolicy: Always envFrom: - configMapRef: - name: api-config + name: security env: - name: KAFKA_BROKERS valueFrom: diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml index d4e37780ab..32a05ca40b 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/google/templates/deployments.yaml @@ -30,7 +30,7 @@ spec: imagePullPolicy: Always envFrom: - configMapRef: - name: api-config + name: security env: - name: KAFKA_BROKERS valueFrom: diff --git a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml index 7315010f33..528c2a086d 100644 --- a/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/sources/charts/twilio/templates/deployments.yaml @@ -30,7 +30,7 @@ spec: imagePullPolicy: Always envFrom: - configMapRef: - name: api-config + name: security env: - name: KAFKA_BROKERS valueFrom: From 0af7322a8885e2d90e7f1f2c01e462098dffe803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Pr=C3=B6schel?= Date: Wed, 21 Apr 2021 10:20:45 +0200 Subject: [PATCH 50/50] Fix missing dependency on security configmap --- .../apps/charts/api/charts/api-admin/templates/deployment.yaml | 2 -- .../api/charts/api-communication/templates/deployment.yaml | 2 -- .../charts/api/charts/api-websocket/templates/deployment.yaml | 2 -- 3 files changed, 6 deletions(-) diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml index 20bc37ab64..919d2d7a2b 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-admin/templates/deployment.yaml @@ -29,8 +29,6 @@ spec: image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always envFrom: - - configMapRef: - name: api-config - configMapRef: name: security env: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml index 5f4d24fc9b..808b812cf4 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-communication/templates/deployment.yaml @@ -29,8 +29,6 @@ spec: image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always envFrom: - - configMapRef: - name: api-config - configMapRef: name: security env: diff --git a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml index 0ff486796a..7b315268ee 100644 --- a/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml +++ b/infrastructure/helm-chart/charts/apps/charts/api/charts/api-websocket/templates/deployment.yaml @@ -29,8 +29,6 @@ spec: image: "{{ .Values.global.containerRegistry}}/{{ .Values.image }}:{{ .Values.global.appImageTag }}" imagePullPolicy: Always envFrom: - - configMapRef: - name: api-config - configMapRef: name: security env: