From 2a463ccf74ccbaea8564e23bc80df3f0247ca357 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 22 Jun 2020 13:17:44 -0400 Subject: [PATCH 01/58] wip(wip): add environment information dialog --- .../components/EnvironmentInfoDialog.tsx | 71 +++++++++++++++++++ .../src/admin/contexts/Cms/graphql.ts | 5 ++ .../admin/plugins/menus/HeadlessCmsMenu.tsx | 25 ++++++- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx new file mode 100644 index 00000000000..d57f676ea43 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { css } from "emotion"; +import { i18n } from "@webiny/app/i18n"; +import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; +import { ButtonDefault, ButtonIcon } from "@webiny/ui/Button"; +import { ReactComponent as CloseIcon } from "./round-close-24px.svg"; + +export type NewContentModelDialogProps = { + open: boolean; + onClose: () => void; + name: string; + url: { + manage: string; + preview: string; + read: string; + }; +}; + +const t = i18n.ns("app-headless-cms/admin/components/environment-selector-dialog"); + +const style = { + narrowDialog: css({ + ".mdc-dialog__surface": { + width: 400, + minWidth: 400 + } + }), +}; + +const EnvironmentInfoDialog: React.FC = ({ + open, + onClose, + name, + url +}) => { + const graphqlApiUrl = process.env.REACT_APP_API_URL; + return ( + + {t`Environment: `}{name} + +
+

{graphqlApiUrl}

+ { + url &&
+

{process.env.REACT_APP_GRAPHQL_API_URL}

+

{`${graphqlApiUrl}${url.manage}`}

+

{`${graphqlApiUrl}${url.preview}`}

+

{`${graphqlApiUrl}${url.read}`}

+
+ } +
+
+ { + onClose + }} + > + } /> + +
+
+
+ ) +}; + +export default EnvironmentInfoDialog; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts b/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts index de7fa4dbd5f..a669705d0a9 100644 --- a/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts +++ b/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts @@ -13,6 +13,11 @@ export const LIST_ENVIRONMENTS_SELECTOR_ENVIRONMENTS = gql` id name isProduction + url { + manage + read + preview + } } } } diff --git a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx index 934e638aec7..709a0c431d2 100644 --- a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx +++ b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx @@ -13,6 +13,7 @@ import { registerPlugins, unregisterPlugin, getPlugin, getPlugins } from "@webin import { AdminGlobalSearchPlugin } from "@webiny/app-admin/types"; import { LIST_MENU_CONTENT_GROUPS_MODELS } from "./../../viewsGraphql"; import get from "lodash/get"; +import EnvironmentInfoDialog from "../../components/EnvironmentInfoDialog"; const style = { itemsList: css({ @@ -38,18 +39,22 @@ const style = { }), environmentLiLabel: css({ marginLeft: 2, - width: 110, + width: 65, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }), changeEnvironmentLiLink: css({ color: "var(--mdc-theme-primary)" + }), + informationLabel: css({ + color: "var(--mdc-theme-primary)" }) }; const HeadlessCmsMenu = ({ Menu, children }) => { const [dialogOpened, setDialogOpened] = useState(false); + const [infoOpened, setInfoOpened] = useState(false); const { hideMenu } = useNavigation(); const { @@ -69,6 +74,7 @@ const HeadlessCmsMenu = ({ Menu, children }) => { ); }, ""); + console.log(currentEnvironment); // Generate "admin-global-search" plugins - enables the user to search content via the global search bar. useEffect(() => { // 1. Unregister all previously registered plugins. @@ -105,6 +111,23 @@ const HeadlessCmsMenu = ({ Menu, children }) => { {currentEnvironment ? currentEnvironment.name : t`N/A`} + +
{ + e.preventDefault(); + e.stopPropagation(); + setInfoOpened(true); + }}> + (i) +
+
+ { + setInfoOpened(false); + }} + name={currentEnvironment ? currentEnvironment.name : t`N/A`} + url={currentEnvironment ? currentEnvironment.environmentAlias.url : undefined} + />
  • From da3d335b24ef709c53b67e321f31846302c017da Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 22 Jun 2020 13:31:42 -0400 Subject: [PATCH 02/58] wip(wip): add surrounding text to api links --- .../src/admin/components/EnvironmentInfoDialog.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx index d57f676ea43..0df0af07f9c 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -47,10 +47,10 @@ const EnvironmentInfoDialog: React.FC = ({

    {graphqlApiUrl}

    { url &&
    -

    {process.env.REACT_APP_GRAPHQL_API_URL}

    -

    {`${graphqlApiUrl}${url.manage}`}

    -

    {`${graphqlApiUrl}${url.preview}`}

    -

    {`${graphqlApiUrl}${url.read}`}

    +

    GraphQL API: {process.env.REACT_APP_GRAPHQL_API_URL}

    +

    Content Delivery API: {`${graphqlApiUrl}${url.read}`}

    +

    Content Preview API: {`${graphqlApiUrl}${url.preview}`}

    +

    Content Management API: {`${graphqlApiUrl}${url.manage}`}

    } From 7feb62006cd424473b07e1784a3b4de5db6ef2cb Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 22 Jun 2020 14:41:48 -0400 Subject: [PATCH 03/58] feat(headless): add info icon and style environment information dialog --- .../components/EnvironmentInfoDialog.tsx | 72 ++++++++++++++----- .../app-headless-cms/src/admin/icons/info.svg | 1 + .../admin/plugins/menus/HeadlessCmsMenu.tsx | 4 +- 3 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 packages/app-headless-cms/src/admin/icons/info.svg diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx index 0df0af07f9c..06866ac870a 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -2,8 +2,8 @@ import React from "react"; import { css } from "emotion"; import { i18n } from "@webiny/app/i18n"; import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; -import { ButtonDefault, ButtonIcon } from "@webiny/ui/Button"; -import { ReactComponent as CloseIcon } from "./round-close-24px.svg"; +import { CopyButton } from "@webiny/ui/Button"; +import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; export type NewContentModelDialogProps = { open: boolean; @@ -21,10 +21,14 @@ const t = i18n.ns("app-headless-cms/admin/components/environment-selector-dialog const style = { narrowDialog: css({ ".mdc-dialog__surface": { - width: 400, - minWidth: 400 + width: 600, + minWidth: 600 } }), + apiUrl: css({ + display: "flex", + alignItems: "center", + }), }; const EnvironmentInfoDialog: React.FC = ({ @@ -33,6 +37,7 @@ const EnvironmentInfoDialog: React.FC = ({ name, url }) => { + const { showSnackbar } = useSnackbar(); const graphqlApiUrl = process.env.REACT_APP_API_URL; return ( = ({ {t`Environment: `}{name}
    -

    {graphqlApiUrl}

    { - url &&
    -

    GraphQL API: {process.env.REACT_APP_GRAPHQL_API_URL}

    -

    Content Delivery API: {`${graphqlApiUrl}${url.read}`}

    -

    Content Preview API: {`${graphqlApiUrl}${url.preview}`}

    -

    Content Management API: {`${graphqlApiUrl}${url.manage}`}

    + url ?
    +

    GraphQL API:

    +
    +

    {process.env.REACT_APP_GRAPHQL_API_URL}

    + + showSnackbar("Successfully copied!") + } + /> +
    +

    Content Delivery API:

    +
    +

    {`${graphqlApiUrl}${url.read}`}

    + + showSnackbar("Successfully copied!") + } + /> +
    +

    Content Preview API:

    +
    +

    {`${graphqlApiUrl}${url.preview}`}

    + + showSnackbar("Successfully copied!") + } + /> +
    +

    Content Management API:

    +
    +

    {`${graphqlApiUrl}${url.manage}`}

    + + showSnackbar("Successfully copied!") + } + /> +
    + :
    + {t`Loading your URL's shortly...`} +
    }
    -
    - { - onClose - }} - > - } /> - -
    ) diff --git a/packages/app-headless-cms/src/admin/icons/info.svg b/packages/app-headless-cms/src/admin/icons/info.svg new file mode 100644 index 00000000000..515a5086e7a --- /dev/null +++ b/packages/app-headless-cms/src/admin/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx index 709a0c431d2..7278727c89f 100644 --- a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx +++ b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx @@ -3,6 +3,7 @@ import { i18n } from "@webiny/app/i18n"; const t = i18n.ns("app-headless-cms/admin/menus"); import { ReactComponent as HeadlessCmsIcon } from "../../icons/devices_other-black-24px.svg"; import { ReactComponent as EnvironmentIcon } from "../../icons/call_split-24px.svg"; +import { ReactComponent as InformationIcon } from "../../icons/info.svg" import { Typography } from "@webiny/ui/Typography"; import { css } from "emotion"; import { useNavigation } from "@webiny/app-admin/plugins/Menu/Navigation/components"; @@ -117,13 +118,14 @@ const HeadlessCmsMenu = ({ Menu, children }) => { e.stopPropagation(); setInfoOpened(true); }}> - (i) +
    { setInfoOpened(false); + hideMenu(); }} name={currentEnvironment ? currentEnvironment.name : t`N/A`} url={currentEnvironment ? currentEnvironment.environmentAlias.url : undefined} From 9079106df3ac84c34baa43c8bfcd6fb9598ff556 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Tue, 23 Jun 2020 16:19:06 -0400 Subject: [PATCH 04/58] wip(wip): add environment info dialog to environment aliases menu --- .../admin/plugins/menus/HeadlessCmsMenu.tsx | 1 - .../EnvironmentAliasesDataList.tsx | 50 ++++++++++++++++++- .../admin/views/EnvironmentAliases/graphql.ts | 5 ++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx index 7278727c89f..b6b5cd7c630 100644 --- a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx +++ b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx @@ -75,7 +75,6 @@ const HeadlessCmsMenu = ({ Menu, children }) => { ); }, ""); - console.log(currentEnvironment); // Generate "admin-global-search" plugins - enables the user to search content via the global search bar. useEffect(() => { // 1. Unregister all previously registered plugins. diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index f76090fbe36..6e67f3466b8 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -1,7 +1,9 @@ -import React from "react"; +import React, { useState } from "react"; import { i18n } from "@webiny/app/i18n"; import { ConfirmationDialog } from "@webiny/ui/ConfirmationDialog"; import { DeleteIcon } from "@webiny/ui/List/DataList/icons"; +import { ReactComponent as InformationIcon } from "../../icons/info.svg"; +import { css } from "emotion"; import { useCrud } from "@webiny/app-admin/hooks/useCrud"; import { Typography } from "@webiny/ui/Typography"; @@ -15,11 +17,37 @@ import { ListActions } from "@webiny/ui/List"; import { Link } from "@webiny/react-router"; +import EnvironmentInfoDialog from "../../components/EnvironmentInfoDialog"; const t = i18n.ns("app-headless-cms/admin/environmentAliases/data-list"); +const style = { + informationLabel: css({ + color: "var(--mdc-theme-primary)" + }), + icon: css({ + width: 16, + height: 16, + marginTop: "4px", + marginLeft: "10px" + }), + environmentText: css({ + display: "flex", + flexDirection: "row" + }) +}; + const EnvironmentAliasesDataList = () => { const { actions, list } = useCrud(); + const [infoOpened, setInfoOpened] = useState(false); + const [selectedInfo, setSelectedInfo] = useState({ + name: t`N\A`, + url: { + manage: "", + preview: "", + read: "" + }, + }); return ( { {data.map(item => ( select(item)}> - {item.name}{" "} +
    + {item.name}{" "} + +
    { + e.preventDefault(); + e.stopPropagation(); + setInfoOpened(true); + setSelectedInfo(item); + }}> + +
    +
    +
    {item.default && ( {t`(default)`} )} @@ -69,6 +109,12 @@ const EnvironmentAliasesDataList = () => { ) }) : t`No environment.`} + setInfoOpened(false)} + name={selectedInfo.name} + url={selectedInfo.url} + />
    diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/graphql.ts b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/graphql.ts index 25b82d1e3e2..075ac715076 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/graphql.ts +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/graphql.ts @@ -34,6 +34,11 @@ export const LIST_ENVIRONMENT_ALIASES = gql` name slug createdOn + url { + manage + read + preview + } environment { id name From 9bddab7dd50b0235b5d7130533e74731e1a9b254 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Tue, 23 Jun 2020 16:45:12 -0400 Subject: [PATCH 05/58] wip(headless): add api information dialog to accessTokensDataList --- .../app-headless-cms/src/admin/icons/info.svg | 20 +++++++- .../AccessTokens/AccessTokensDataList.tsx | 51 ++++++++++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/packages/app-headless-cms/src/admin/icons/info.svg b/packages/app-headless-cms/src/admin/icons/info.svg index 515a5086e7a..1adfb79e072 100644 --- a/packages/app-headless-cms/src/admin/icons/info.svg +++ b/packages/app-headless-cms/src/admin/icons/info.svg @@ -1 +1,19 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx b/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx index 2b9380a2518..1c900c65982 100644 --- a/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx @@ -1,9 +1,11 @@ -import React from "react"; +import React, { useState } from "react"; import { i18n } from "@webiny/app/i18n"; import { ConfirmationDialog } from "@webiny/ui/ConfirmationDialog"; import { DeleteIcon } from "@webiny/ui/List/DataList/icons"; import { useCrud } from "@webiny/app-admin/hooks/useCrud"; import { Typography } from "@webiny/ui/Typography"; +import { ReactComponent as InformationIcon } from "../../icons/info.svg"; +import { css } from "emotion"; import { DataList, @@ -15,11 +17,38 @@ import { ListActions } from "@webiny/ui/List"; import { Link } from "@webiny/react-router"; +import EnvironmentInfoDialog from "../../components/EnvironmentInfoDialog"; const t = i18n.ns("app-headless-cms/admin/environmentAliases/data-list"); +const style = { + informationLabel: css({ + color: "var(--mdc-theme-primary)" + }), + icon: css({ + width: 16, + height: 16, + marginTop: "4px", + marginLeft: "10px" + }), + environmentText: css({ + display: "flex", + flexDirection: "row" + }) +}; + + const EnvironmentAliasesDataList = () => { const { actions, list } = useCrud(); + const [infoOpened, setInfoOpened] = useState(false); + const [selectedInfo, setSelectedInfo] = useState({ + name: t`N\A`, + url: { + manage: "", + preview: "", + read: "" + }, + }); return ( { {data.map(item => ( select(item)}> - {item.name}{" "} +
    + {item.name}{" "} + +
    { + e.preventDefault(); + e.stopPropagation(); + setInfoOpened(true); + setSelectedInfo(item); + }}> + +
    +
    + setInfoOpened(false)} + name={selectedInfo.name} + url={selectedInfo.url} + /> +
    {item.default && ( {t`(default)`} )} From ba9f211af73103d7204f772707414d1efaff6859 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Wed, 24 Jun 2020 10:02:46 -0400 Subject: [PATCH 06/58] wip: add info dialog next to info icon --- .../EnvironmentAliasesDataList.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index 6e67f3466b8..57d69b8ade3 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -88,7 +88,13 @@ const EnvironmentAliasesDataList = () => { }}> - + + setInfoOpened(false)} + name={selectedInfo.name} + url={selectedInfo.url} + /> {item.default && ( {t`(default)`} @@ -109,12 +115,6 @@ const EnvironmentAliasesDataList = () => { ) }) : t`No environment.`} - setInfoOpened(false)} - name={selectedInfo.name} - url={selectedInfo.url} - />
    From 0f693ada3fdace6d350147f0226d4c7a3b8f4a3b Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Wed, 24 Jun 2020 13:42:19 -0400 Subject: [PATCH 07/58] wip(headless): remove unneeded dialog from access tokens list --- .../AccessTokens/AccessTokensDataList.tsx | 50 +------------------ 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx b/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx index 1c900c65982..5d06bf2e5a7 100644 --- a/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/AccessTokens/AccessTokensDataList.tsx @@ -1,11 +1,9 @@ -import React, { useState } from "react"; +import React from "react"; import { i18n } from "@webiny/app/i18n"; import { ConfirmationDialog } from "@webiny/ui/ConfirmationDialog"; import { DeleteIcon } from "@webiny/ui/List/DataList/icons"; import { useCrud } from "@webiny/app-admin/hooks/useCrud"; import { Typography } from "@webiny/ui/Typography"; -import { ReactComponent as InformationIcon } from "../../icons/info.svg"; -import { css } from "emotion"; import { DataList, @@ -17,38 +15,12 @@ import { ListActions } from "@webiny/ui/List"; import { Link } from "@webiny/react-router"; -import EnvironmentInfoDialog from "../../components/EnvironmentInfoDialog"; const t = i18n.ns("app-headless-cms/admin/environmentAliases/data-list"); -const style = { - informationLabel: css({ - color: "var(--mdc-theme-primary)" - }), - icon: css({ - width: 16, - height: 16, - marginTop: "4px", - marginLeft: "10px" - }), - environmentText: css({ - display: "flex", - flexDirection: "row" - }) -}; - const EnvironmentAliasesDataList = () => { const { actions, list } = useCrud(); - const [infoOpened, setInfoOpened] = useState(false); - const [selectedInfo, setSelectedInfo] = useState({ - name: t`N\A`, - url: { - manage: "", - preview: "", - read: "" - }, - }); return ( { {data.map(item => ( select(item)}> -
    - {item.name}{" "} - -
    { - e.preventDefault(); - e.stopPropagation(); - setInfoOpened(true); - setSelectedInfo(item); - }}> - -
    -
    - setInfoOpened(false)} - name={selectedInfo.name} - url={selectedInfo.url} - /> -
    + {item.name}{" "} {item.default && ( {t`(default)`} )} From c24f4c31b8dfdc6e94f1aa9cb915f7d2864f7a48 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 25 Jun 2020 13:22:23 -0400 Subject: [PATCH 08/58] feat(headless): add all environment aliases to info and styling --- .../components/EnvironmentInfoDialog.tsx | 110 +++++++++++------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx index 06866ac870a..f7c54c3b9b7 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -1,9 +1,11 @@ -import React from "react"; +import React, { useState } from "react"; +import { useQuery } from '@apollo/react-hooks'; import { css } from "emotion"; import { i18n } from "@webiny/app/i18n"; import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; import { CopyButton } from "@webiny/ui/Button"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; +import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; export type NewContentModelDialogProps = { open: boolean; @@ -21,14 +23,22 @@ const t = i18n.ns("app-headless-cms/admin/components/environment-selector-dialog const style = { narrowDialog: css({ ".mdc-dialog__surface": { - width: 600, - minWidth: 600 + width: 800, + minWidth: 800 } }), apiUrl: css({ display: "flex", - alignItems: "center", + alignItems: "center" }), + alias: css({ + fontSize: "1.25rem", + fontWeight: "bold" + }), + aliasTitle: css({ + textDecoration: "underline", + minWidth: "200px" + }) }; const EnvironmentInfoDialog: React.FC = ({ @@ -39,6 +49,14 @@ const EnvironmentInfoDialog: React.FC = ({ }) => { const { showSnackbar } = useSnackbar(); const graphqlApiUrl = process.env.REACT_APP_API_URL; + const [aliases, setAliases] = useState([]); + + useQuery(LIST_ENVIRONMENT_ALIASES, { + onCompleted: data => { + setAliases(data.cms.environmentAliases.data.filter((elem) => elem.environment.name === name)); + } + }); + return ( = ({
    { - url ?
    -

    GraphQL API:

    -
    -

    {process.env.REACT_APP_GRAPHQL_API_URL}

    - - showSnackbar("Successfully copied!") - } - /> -
    -

    Content Delivery API:

    -
    -

    {`${graphqlApiUrl}${url.read}`}

    - - showSnackbar("Successfully copied!") - } - /> -
    -

    Content Preview API:

    -
    -

    {`${graphqlApiUrl}${url.preview}`}

    - - showSnackbar("Successfully copied!") - } - /> -
    -

    Content Management API:

    -
    -

    {`${graphqlApiUrl}${url.manage}`}

    - - showSnackbar("Successfully copied!") - } - /> -
    + url && aliases.length > 0 ? +
    + { + aliases.map((elem) => { + return( +
    +

    Alias: {elem.name}

    +
    +

    GraphQL API:

    +

    {process.env.REACT_APP_GRAPHQL_API_URL}

    + showSnackbar("Successfully copied!")} + /> +
    +
    +

    Content Delivery API:

    +

    {`${graphqlApiUrl}${elem.url.read}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    +

    Content Preview API:

    +

    {`${graphqlApiUrl}${elem.url.preview}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    +

    Content Management API:

    +

    {`${graphqlApiUrl}${elem.url.manage}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    + ) + }) + }
    :
    {t`Loading your URL's shortly...`} From 2283f950b03d843dba23e1bcd0ab6576141db195 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 25 Jun 2020 13:31:33 -0400 Subject: [PATCH 09/58] fix(headless): add dependency for apollo hooks --- packages/app-headless-cms/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app-headless-cms/package.json b/packages/app-headless-cms/package.json index 654d3c7e36c..452211780b2 100644 --- a/packages/app-headless-cms/package.json +++ b/packages/app-headless-cms/package.json @@ -13,6 +13,7 @@ ], "license": "MIT", "dependencies": { + "@apollo/react-hooks": "^3.1.5", "@babel/runtime": "^7.5.5", "@emotion/core": "^10.0.17", "@emotion/styled": "^10.0.17", From 162e0080363123da21c14ae1c596d41c2afa414b Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 29 Jun 2020 16:18:53 -0400 Subject: [PATCH 10/58] wip(headless): move info icon to menu footer --- packages/app-admin/package.json | 1 + packages/app-admin/src/assets/icons/info.svg | 8 ++ .../src}/components/EnvironmentInfoDialog.tsx | 30 ++-- .../views/EnvironmentAliases/graphql.ts | 136 ++++++++++++++++++ .../src/plugins/Menu/Navigation/index.tsx | 41 +++++- packages/app-admin/tsconfig.json | 3 +- .../app-headless-cms/src/admin/icons/info.svg | 19 --- .../admin/plugins/menus/HeadlessCmsMenu.tsx | 29 ---- 8 files changed, 206 insertions(+), 61 deletions(-) create mode 100644 packages/app-admin/src/assets/icons/info.svg rename packages/{app-headless-cms/src/admin => app-admin/src}/components/EnvironmentInfoDialog.tsx (82%) create mode 100644 packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts delete mode 100644 packages/app-headless-cms/src/admin/icons/info.svg diff --git a/packages/app-admin/package.json b/packages/app-admin/package.json index 40de3791e9e..689f2d0c418 100644 --- a/packages/app-admin/package.json +++ b/packages/app-admin/package.json @@ -19,6 +19,7 @@ "@webiny/plugins": "^4.1.0", "@webiny/react-router": "^4.1.0", "@webiny/ui": "^4.1.0", + "@webiny/app-headless-cms": "^4.1.0", "apollo-client": "^2.6.8", "bytes": "^3.1.0", "classnames": "^2.2.6", diff --git a/packages/app-admin/src/assets/icons/info.svg b/packages/app-admin/src/assets/icons/info.svg new file mode 100644 index 00000000000..d50ac3989a0 --- /dev/null +++ b/packages/app-admin/src/assets/icons/info.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx similarity index 82% rename from packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx rename to packages/app-admin/src/components/EnvironmentInfoDialog.tsx index f7c54c3b9b7..287cde985f9 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx @@ -5,7 +5,7 @@ import { i18n } from "@webiny/app/i18n"; import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; import { CopyButton } from "@webiny/ui/Button"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; -import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; +import { LIST_ENVIRONMENT_ALIASES } from "./views/EnvironmentAliases/graphql"; export type NewContentModelDialogProps = { open: boolean; @@ -18,7 +18,7 @@ export type NewContentModelDialogProps = { }; }; -const t = i18n.ns("app-headless-cms/admin/components/environment-selector-dialog"); +const t = i18n.ns("app-admin/navigation"); const style = { narrowDialog: css({ @@ -38,6 +38,14 @@ const style = { aliasTitle: css({ textDecoration: "underline", minWidth: "200px" + }), + api: css({ + fontSize: "1.25rem", + fontWeight: "bold", + minWidth: "200px" + }), + aliasContainer: css({ + marginTop: "10px" }) }; @@ -70,19 +78,19 @@ const EnvironmentInfoDialog: React.FC = ({ { url && aliases.length > 0 ?
    +
    +

    GraphQL URL:

    +

    {process.env.REACT_APP_GRAPHQL_API_URL}

    + showSnackbar("Successfully copied!")} + /> +
    { aliases.map((elem) => { return( -
    +

    Alias: {elem.name}

    -
    -

    GraphQL API:

    -

    {process.env.REACT_APP_GRAPHQL_API_URL}

    - showSnackbar("Successfully copied!")} - /> -

    Content Delivery API:

    {`${graphqlApiUrl}${elem.url.read}`}

    diff --git a/packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts b/packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts new file mode 100644 index 00000000000..075ac715076 --- /dev/null +++ b/packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts @@ -0,0 +1,136 @@ +import gql from "graphql-tag"; + +const fields = ` + id + name + slug + description + environment { + id + name + } +`; + +export const LIST_ENVIRONMENT_ALIASES = gql` + query listEnvironmentAliases( + $where: JSON + $sort: JSON + $search: CmsSearchInput + $limit: Int + $after: String + $before: String + ) { + cms { + environmentAliases: listEnvironmentAliases( + where: $where + sort: $sort + search: $search + limit: $limit + after: $after + before: $before + ) { + data { + id + name + slug + createdOn + url { + manage + read + preview + } + environment { + id + name + } + } + meta { + cursors { + next + previous + } + hasNextPage + hasPreviousPage + totalCount + } + } + } + } +`; + +export const GET_ENVIRONMENT_ALIAS_BY_SLUG = gql` + query getEnvironmentAliasBySlug($slug: String) { + cms { + getEnvironmentAlias(where: { slug: $slug }) { + data { + name + id + } + } + } + } +`; + +export const READ_ENVIRONMENT_ALIAS = gql` + query getEnvironmentAlias($id: ID!) { + cms { + environmentAlias: getEnvironmentAlias(id: $id){ + data { + ${fields} + } + error { + code + message + } + } + } + } +`; + +export const CREATE_ENVIRONMENT_ALIAS = gql` + mutation createEnvironmentAlias($data: CmsEnvironmentAliasInput!){ + cms { + environmentAlias: createEnvironmentAlias(data: $data) { + data { + ${fields} + } + error { + code + message + data + } + } + } + } +`; + +export const UPDATE_ENVIRONMENT_ALIAS = gql` + mutation updateEnvironmentAlias($id: ID!, $data: CmsEnvironmentAliasInput!){ + cms { + environmentAlias: updateEnvironmentAlias(id: $id, data: $data) { + data { + ${fields} + } + error { + code + message + data + } + } + } + } +`; + +export const DELETE_ENVIRONMENT_ALIAS = gql` + mutation deleteEnvironmentAlias($id: ID!) { + cms { + deleteEnvironmentAlias(id: $id) { + data + error { + code + message + } + } + } + } +`; diff --git a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx index 9058ba03dde..57528cada45 100755 --- a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx +++ b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx @@ -1,26 +1,44 @@ -import React, { useMemo, useEffect } from "react"; +import React, { useMemo, useState, useEffect } from "react"; import { sortBy } from "lodash"; import { Drawer, DrawerContent, DrawerHeader } from "@webiny/ui/Drawer"; import { List, ListItem, ListItemGraphic } from "@webiny/ui/List"; import { IconButton } from "@webiny/ui/Button"; import { Icon } from "@webiny/ui/Icon"; +import { css } from "emotion"; import { getPlugin, getPlugins } from "@webiny/plugins"; import { AdminHeaderLogoPlugin, AdminMenuPlugin } from "@webiny/app-admin/types"; import { useNavigation, Menu, Item, Section } from "./components"; import { logoStyle, MenuFooter, MenuHeader, navContent, navHeader, subFooter } from "./Styled"; +import { useCms } from "@webiny/app-headless-cms/admin/hooks"; import { ReactComponent as MenuIcon } from "@webiny/app-admin/assets/icons/baseline-menu-24px.svg"; import { ReactComponent as DocsIcon } from "@webiny/app-admin/assets/icons/icon-documentation.svg"; import { ReactComponent as CommunityIcon } from "@webiny/app-admin/assets/icons/icon-community.svg"; import { ReactComponent as GithubIcon } from "@webiny/app-admin/assets/icons/github-brands.svg"; +import { ReactComponent as InfoIcon } from "@webiny/app-admin/assets/icons/info.svg"; +import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; import { i18n } from "@webiny/app/i18n"; const t = i18n.ns("app-admin/navigation"); +const style = { + environmentContainer: css({ + color: "var(--mdc-theme-text-secondary-on-background)" + }), + infoContainer: css({ + alignSelf: "center", + }) +}; + const Navigation = () => { const { hideMenu, menuIsShown, initSections } = useNavigation(); + const [infoOpened, setInfoOpened] = useState(false); useEffect(initSections, []); + const { + environments: { currentEnvironment } + } = useCms(); + const logo = useMemo(() => { const logoPlugin = getPlugin("admin-header-logo"); if (logoPlugin) { @@ -54,6 +72,27 @@ const Navigation = () => { {menus} +
    { + e.preventDefault(); + e.stopPropagation(); + setInfoOpened(true); + }}> + + + }/> + + {t`API information`} + { + setInfoOpened(false); + }} + name={currentEnvironment ? currentEnvironment.name : t`N/A`} + url={currentEnvironment ? currentEnvironment.environmentAlias.url : undefined} + /> + +
    diff --git a/packages/app-admin/tsconfig.json b/packages/app-admin/tsconfig.json index 2a48a9349a4..a047eee6edf 100644 --- a/packages/app-admin/tsconfig.json +++ b/packages/app-admin/tsconfig.json @@ -6,6 +6,7 @@ { "path": "../app" }, { "path": "../plugins" }, { "path": "../react-router" }, - { "path": "../form" } + { "path": "../form" }, + { "path": "../app-headless-cms" } ] } diff --git a/packages/app-headless-cms/src/admin/icons/info.svg b/packages/app-headless-cms/src/admin/icons/info.svg deleted file mode 100644 index 1adfb79e072..00000000000 --- a/packages/app-headless-cms/src/admin/icons/info.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx index b6b5cd7c630..febf09a5fda 100644 --- a/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx +++ b/packages/app-headless-cms/src/admin/plugins/menus/HeadlessCmsMenu.tsx @@ -3,7 +3,6 @@ import { i18n } from "@webiny/app/i18n"; const t = i18n.ns("app-headless-cms/admin/menus"); import { ReactComponent as HeadlessCmsIcon } from "../../icons/devices_other-black-24px.svg"; import { ReactComponent as EnvironmentIcon } from "../../icons/call_split-24px.svg"; -import { ReactComponent as InformationIcon } from "../../icons/info.svg" import { Typography } from "@webiny/ui/Typography"; import { css } from "emotion"; import { useNavigation } from "@webiny/app-admin/plugins/Menu/Navigation/components"; @@ -14,7 +13,6 @@ import { registerPlugins, unregisterPlugin, getPlugin, getPlugins } from "@webin import { AdminGlobalSearchPlugin } from "@webiny/app-admin/types"; import { LIST_MENU_CONTENT_GROUPS_MODELS } from "./../../viewsGraphql"; import get from "lodash/get"; -import EnvironmentInfoDialog from "../../components/EnvironmentInfoDialog"; const style = { itemsList: css({ @@ -27,10 +25,6 @@ const style = { paddingBottom: 2 } }), - icon: css({ - width: 16, - height: 16 - }), environmentLi: css({ color: "var(--mdc-theme-text-secondary-on-background)" }), @@ -40,22 +34,17 @@ const style = { }), environmentLiLabel: css({ marginLeft: 2, - width: 65, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }), changeEnvironmentLiLink: css({ color: "var(--mdc-theme-primary)" - }), - informationLabel: css({ - color: "var(--mdc-theme-primary)" }) }; const HeadlessCmsMenu = ({ Menu, children }) => { const [dialogOpened, setDialogOpened] = useState(false); - const [infoOpened, setInfoOpened] = useState(false); const { hideMenu } = useNavigation(); const { @@ -111,24 +100,6 @@ const HeadlessCmsMenu = ({ Menu, children }) => { {currentEnvironment ? currentEnvironment.name : t`N/A`} - -
    { - e.preventDefault(); - e.stopPropagation(); - setInfoOpened(true); - }}> - -
    -
    - { - setInfoOpened(false); - hideMenu(); - }} - name={currentEnvironment ? currentEnvironment.name : t`N/A`} - url={currentEnvironment ? currentEnvironment.environmentAlias.url : undefined} - />
  • From 8569cd7442db59feb0d81e5d8b0b8766c45c9539 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 29 Jun 2020 18:44:41 -0400 Subject: [PATCH 11/58] wip(headless): fix cyclical dependency for app-admin --- packages/app-admin/package.json | 1 - packages/app-admin/tsconfig.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/app-admin/package.json b/packages/app-admin/package.json index 730d34c07a0..3aeacf5cd0e 100644 --- a/packages/app-admin/package.json +++ b/packages/app-admin/package.json @@ -16,7 +16,6 @@ "@emotion/styled": "^10.0.17", "@svgr/webpack": "^4.3.2", "@webiny/app": "^4.2.0", - "@webiny/app-headless-cms": "^4.2.0", "@webiny/form": "^4.2.0", "@webiny/plugins": "^4.2.0", "@webiny/react-router": "^4.2.0", diff --git a/packages/app-admin/tsconfig.json b/packages/app-admin/tsconfig.json index a047eee6edf..2a48a9349a4 100644 --- a/packages/app-admin/tsconfig.json +++ b/packages/app-admin/tsconfig.json @@ -6,7 +6,6 @@ { "path": "../app" }, { "path": "../plugins" }, { "path": "../react-router" }, - { "path": "../form" }, - { "path": "../app-headless-cms" } + { "path": "../form" } ] } From 983466b8202c8c86c01e8a574396435eeda2a676 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 29 Jun 2020 20:43:09 -0400 Subject: [PATCH 12/58] wip(headless): add call to get current environment --- packages/app-admin/package.json | 1 - .../src/plugins/Menu/Navigation/index.tsx | 16 ++- packages/app-admin/tsconfig.json | 3 +- .../components/EnvironmentInfoDialog.tsx | 133 ++++++++++++++++++ .../app-headless-cms/src/admin/icons/info.svg | 8 ++ 5 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx create mode 100644 packages/app-headless-cms/src/admin/icons/info.svg diff --git a/packages/app-admin/package.json b/packages/app-admin/package.json index 730d34c07a0..3aeacf5cd0e 100644 --- a/packages/app-admin/package.json +++ b/packages/app-admin/package.json @@ -16,7 +16,6 @@ "@emotion/styled": "^10.0.17", "@svgr/webpack": "^4.3.2", "@webiny/app": "^4.2.0", - "@webiny/app-headless-cms": "^4.2.0", "@webiny/form": "^4.2.0", "@webiny/plugins": "^4.2.0", "@webiny/react-router": "^4.2.0", diff --git a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx index d2be1e6bd3b..257f33d8de2 100755 --- a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx +++ b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx @@ -9,7 +9,6 @@ import { getPlugin, getPlugins } from "@webiny/plugins"; import { AdminMenuLogoPlugin, AdminMenuPlugin } from "@webiny/app-admin/types"; import { useNavigation, Menu, Item, Section } from "./components"; import { logoStyle, MenuFooter, MenuHeader, navContent, navHeader, subFooter } from "./Styled"; -import { useCms } from "@webiny/app-headless-cms/admin/hooks"; import { ReactComponent as MenuIcon } from "@webiny/app-admin/assets/icons/baseline-menu-24px.svg"; import { ReactComponent as DocsIcon } from "@webiny/app-admin/assets/icons/icon-documentation.svg"; import { ReactComponent as CommunityIcon } from "@webiny/app-admin/assets/icons/icon-community.svg"; @@ -20,6 +19,8 @@ import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoD import { i18n } from "@webiny/app/i18n"; const t = i18n.ns("app-admin/navigation"); +import get from "lodash.get"; + const style = { environmentContainer: css({ color: "var(--mdc-theme-text-secondary-on-background)" @@ -29,15 +30,20 @@ const style = { }) }; +const getCurrentEnvironmentFromLocalStorage = () => { + try { + return JSON.parse(get(window, "localStorage.cms_environment")); + } catch { + return null; + } +}; + const Navigation = () => { const { hideMenu, menuIsShown, initSections } = useNavigation(); const [infoOpened, setInfoOpened] = useState(false); useEffect(initSections, []); - - const { - environments: { currentEnvironment } - } = useCms(); + const currentEnvironment = getCurrentEnvironmentFromLocalStorage(); const logo = useMemo(() => { const logoPlugin = getPlugin("admin-menu-logo"); diff --git a/packages/app-admin/tsconfig.json b/packages/app-admin/tsconfig.json index a047eee6edf..2a48a9349a4 100644 --- a/packages/app-admin/tsconfig.json +++ b/packages/app-admin/tsconfig.json @@ -6,7 +6,6 @@ { "path": "../app" }, { "path": "../plugins" }, { "path": "../react-router" }, - { "path": "../form" }, - { "path": "../app-headless-cms" } + { "path": "../form" } ] } diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx new file mode 100644 index 00000000000..5c5eb40c483 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -0,0 +1,133 @@ +import React, { useState } from "react"; +import { useQuery } from '@apollo/react-hooks'; +import { css } from "emotion"; +import { i18n } from "@webiny/app/i18n"; +import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; +import { CopyButton } from "@webiny/ui/Button"; +import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; +import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; + +export type NewContentModelDialogProps = { + open: boolean; + onClose: () => void; + name: string; + url: { + manage: string; + preview: string; + read: string; + }; +}; + +const t = i18n.ns("app-headless-cms/admin/menus"); + +const style = { + narrowDialog: css({ + ".mdc-dialog__surface": { + width: 800, + minWidth: 800 + } + }), + apiUrl: css({ + display: "flex", + alignItems: "center" + }), + alias: css({ + fontSize: "1.25rem", + fontWeight: "bold" + }), + aliasTitle: css({ + textDecoration: "underline", + minWidth: "200px" + }), + api: css({ + fontSize: "1.25rem", + fontWeight: "bold", + minWidth: "200px" + }), + aliasContainer: css({ + marginTop: "10px" + }) +}; + +const EnvironmentInfoDialog: React.FC = ({ + open, + onClose, + name, + url +}) => { + const { showSnackbar } = useSnackbar(); + const graphqlApiUrl = process.env.REACT_APP_API_URL; + const [aliases, setAliases] = useState([]); + + useQuery(LIST_ENVIRONMENT_ALIASES, { + onCompleted: data => { + setAliases(data.cms.environmentAliases.data.filter((elem) => elem.environment.name === name)); + } + }); + + return ( + + {t`Environment: `}{name} + +
    + { + url && aliases.length > 0 ? +
    +
    +

    GraphQL URL:

    +

    {process.env.REACT_APP_GRAPHQL_API_URL}

    + showSnackbar("Successfully copied!")} + /> +
    + { + aliases.map((elem) => { + return( +
    +

    Alias: {elem.name}

    +
    +

    Content Delivery API:

    +

    {`${graphqlApiUrl}${elem.url.read}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    +

    Content Preview API:

    +

    {`${graphqlApiUrl}${elem.url.preview}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    +

    Content Management API:

    +

    {`${graphqlApiUrl}${elem.url.manage}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    + ) + }) + } +
    + :
    + {t`Loading your URL's shortly...`} +
    + } +
    +
    +
    + ) +}; + +export default EnvironmentInfoDialog; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/icons/info.svg b/packages/app-headless-cms/src/admin/icons/info.svg new file mode 100644 index 00000000000..98202899d2f --- /dev/null +++ b/packages/app-headless-cms/src/admin/icons/info.svg @@ -0,0 +1,8 @@ + + + + + + + + From 49e3e778a7bc212215968dc3386c08fece78490e Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 29 Jun 2020 22:01:35 -0400 Subject: [PATCH 13/58] wip: add apollo react hooks to app-headless-cms --- packages/app-headless-cms/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app-headless-cms/package.json b/packages/app-headless-cms/package.json index 7fa36a8546d..d0ec573fcc3 100644 --- a/packages/app-headless-cms/package.json +++ b/packages/app-headless-cms/package.json @@ -13,6 +13,7 @@ ], "license": "MIT", "dependencies": { + "@apollo/react-hooks": "^3.1.5", "@babel/runtime": "^7.5.5", "@emotion/core": "^10.0.17", "@emotion/styled": "^10.0.17", From c776109f74e31e9b1db09146792e1dc61c07a31d Mon Sep 17 00:00:00 2001 From: Pavel Denisjuk Date: Tue, 30 Jun 2020 10:20:01 +0200 Subject: [PATCH 14/58] fix(app-plugin-admin-welcome-screen): override route-root to render at "/" path --- packages/app-plugin-admin-welcome-screen/src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-plugin-admin-welcome-screen/src/index.tsx b/packages/app-plugin-admin-welcome-screen/src/index.tsx index 24d7ddfebbe..b3dcb8a59d0 100644 --- a/packages/app-plugin-admin-welcome-screen/src/index.tsx +++ b/packages/app-plugin-admin-welcome-screen/src/index.tsx @@ -6,7 +6,7 @@ import Welcome from "./components/Welcome"; export default () => [ { - name: "route-welcome", + name: "route-root", type: "route", route: ( Date: Mon, 29 Jun 2020 21:27:11 +0200 Subject: [PATCH 15/58] fix: remove "AdminMenuContentSectionPlugin" type --- packages/app-admin/src/types.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/app-admin/src/types.ts b/packages/app-admin/src/types.ts index 460f59b5531..58d77ab5e28 100644 --- a/packages/app-admin/src/types.ts +++ b/packages/app-admin/src/types.ts @@ -63,11 +63,6 @@ export type AdminMenuPlugin = Plugin & { order?: number; }; -export type AdminMenuContentSectionPlugin = Plugin & { - type: "admin-menu-content-section"; - render(props: { Section: typeof Section; Item: typeof Item }): React.ReactNode; -}; - /** * Enables adding custom header elements to the left side of the top bar. * @see https://docs.webiny.com/docs/webiny-apps/admin/development/plugins-reference/app#admin-header-left From 066b6b3835b0c6e7113d242edcccdddfa6b03855 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Mon, 29 Jun 2020 21:38:03 +0200 Subject: [PATCH 16/58] fix: replace "Content" section in the main menu with "Page Builder" and "Form Builder" --- .../src/admin/plugins/menus.tsx | 47 ++++++++++++------- .../src/admin/plugins/menus.tsx | 14 ++---- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/app-form-builder/src/admin/plugins/menus.tsx b/packages/app-form-builder/src/admin/plugins/menus.tsx index 04f461ec405..f6c78da76b7 100644 --- a/packages/app-form-builder/src/admin/plugins/menus.tsx +++ b/packages/app-form-builder/src/admin/plugins/menus.tsx @@ -1,24 +1,39 @@ import React from "react"; +import { ReactComponent as PagesIcon } from "@webiny/app-page-builder/admin/assets/round-ballot-24px.svg"; import { i18n } from "@webiny/app/i18n"; import { SecureView } from "@webiny/app-security/components"; -import { AdminMenuContentSectionPlugin } from "@webiny/app-admin/types"; +import { AdminMenuPlugin } from "@webiny/app-admin/types"; const t = i18n.ns("app-form-builder/admin/menus"); +const ROLE_FORMS_EDITOR = ["forms:form:crud"]; -const ROLE_FORMS_EDITOR = ["forms:settings"]; +const plugin: AdminMenuPlugin = { + type: "admin-menu", + name: "admin-menu-form-builder", + render({ Menu, Section, Item }) { + return ( + + {({ scopes }) => { + const { forms } = scopes; + if (!forms) { + return null; + } -export default [ - { - type: "admin-menu-content-section", - name: "menu-content-section-forms", - render({ Section, Item }) { - return ( - -
    - -
    -
    - ); - } + return ( + }> +
    + {forms && } +
    +
    + ); + }} +
    + ); } -] as AdminMenuContentSectionPlugin[]; +}; + +export default plugin; diff --git a/packages/app-page-builder/src/admin/plugins/menus.tsx b/packages/app-page-builder/src/admin/plugins/menus.tsx index 057d8501fd6..ac1863cfe1a 100644 --- a/packages/app-page-builder/src/admin/plugins/menus.tsx +++ b/packages/app-page-builder/src/admin/plugins/menus.tsx @@ -1,9 +1,8 @@ import React from "react"; import { ReactComponent as PagesIcon } from "@webiny/app-page-builder/admin/assets/round-ballot-24px.svg"; import { i18n } from "@webiny/app/i18n"; -import { getPlugins } from "@webiny/plugins"; import { SecureView } from "@webiny/app-security/components"; -import { AdminMenuPlugin, AdminMenuContentSectionPlugin } from "@webiny/app-admin/types"; +import { AdminMenuPlugin } from "@webiny/app-admin/types"; const t = i18n.ns("app-form-builder/admin/menus"); @@ -13,7 +12,7 @@ const ROLE_PB_EDITOR = ["pb:page:crud"]; const plugin: AdminMenuPlugin = { type: "admin-menu", - name: "menu-content", + name: "admin-menu-page-builder", render({ Menu, Section, Item }) { return ( }> + }>
    {categories && ( @@ -38,13 +37,6 @@ const plugin: AdminMenuPlugin = { {editor && } {menus && }
    - {getPlugins("admin-menu-content-section").map( - (plugin: AdminMenuContentSectionPlugin) => ( - - {plugin.render({ Section, Item })} - - ) - )}
    ); }} From 7e462093e0cabe48764f74668148400e8285796c Mon Sep 17 00:00:00 2001 From: Pavel Denisjuk Date: Tue, 30 Jun 2020 10:57:52 +0200 Subject: [PATCH 17/58] fix(app-template): sort routes so * is always the last item --- packages/app-template/src/Routes.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/app-template/src/Routes.tsx b/packages/app-template/src/Routes.tsx index 61223d8267f..3b90bc1ea44 100644 --- a/packages/app-template/src/Routes.tsx +++ b/packages/app-template/src/Routes.tsx @@ -8,16 +8,23 @@ export const Routes = () => { const pathA = a.route.props.path || "*"; const pathB = b.route.props.path || "*"; + // This will sort paths at the very bottom of the list + if (pathA === "/" && pathB === "*") { + return -1; + } + + // This will push * and / to the bottom of the list if (pathA === "*" || pathA === "/") { return 1; } + // This will push * and / to the bottom of the list if (pathB === "*" || pathB === "/") { return -1; } return 0; }); - + return {plugins.map(pl => React.cloneElement(pl.route, { key: pl.name }))}; }; From a6f23964e5c2282eb2ea15d9a95cbd1d9e85a52c Mon Sep 17 00:00:00 2001 From: Pavel Denisjuk Date: Tue, 30 Jun 2020 10:58:25 +0200 Subject: [PATCH 18/58] fix(app-template-site): remove root route --- packages/app-template-site/src/index.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/app-template-site/src/index.tsx b/packages/app-template-site/src/index.tsx index f044420abc2..b10ea7ce5b6 100644 --- a/packages/app-template-site/src/index.tsx +++ b/packages/app-template-site/src/index.tsx @@ -64,14 +64,7 @@ export default createTemplate(opts => { } ]; - const defaultRoute = { - type: "route", - name: "route-root", - route: } /> - }; - const otherPlugins = [ - defaultRoute, fileUploadPlugin(), imagePlugin(), pageBuilderPlugins(), From 1373dccf76708b9822fafb0ad46210ee84665adc Mon Sep 17 00:00:00 2001 From: Pavel Denisjuk Date: Tue, 30 Jun 2020 10:58:47 +0200 Subject: [PATCH 19/58] fix(app-template-admin): remove root route --- packages/app-template-admin/src/index.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/app-template-admin/src/index.tsx b/packages/app-template-admin/src/index.tsx index 526fc31cd1b..d44742e7d3e 100644 --- a/packages/app-template-admin/src/index.tsx +++ b/packages/app-template-admin/src/index.tsx @@ -97,13 +97,6 @@ export default createTemplate(opts => { ]; const routes: RoutePlugin[] = [ - { - type: "route", - name: "route-root", - route: ( - } /> - ) - }, { type: "route", name: "route-not-found", From e39cc125dfe05b32660d2eea60cda378e2af77cd Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Mon, 29 Jun 2020 11:59:42 +0530 Subject: [PATCH 20/58] feat(api-headless-cms): add `environmentAliases` update `environment` to have aliases instead of alias --- .../src/plugins/graphql/environment.ts | 2 +- .../src/plugins/models/environment.model.ts | 36 +++++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/api-headless-cms/src/plugins/graphql/environment.ts b/packages/api-headless-cms/src/plugins/graphql/environment.ts index e38e73f7de6..0909715a688 100644 --- a/packages/api-headless-cms/src/plugins/graphql/environment.ts +++ b/packages/api-headless-cms/src/plugins/graphql/environment.ts @@ -17,7 +17,7 @@ export default { name: String description: String createdFrom: CmsEnvironment - environmentAlias: CmsEnvironmentAlias + environmentAliases: [CmsEnvironmentAlias] isProduction: Boolean } diff --git a/packages/api-headless-cms/src/plugins/models/environment.model.ts b/packages/api-headless-cms/src/plugins/models/environment.model.ts index a46bd5fe1e2..6cc3d328ebe 100644 --- a/packages/api-headless-cms/src/plugins/models/environment.model.ts +++ b/packages/api-headless-cms/src/plugins/models/environment.model.ts @@ -16,15 +16,18 @@ export default ({ createBase, context }: { createBase: Function; context: CmsCon })), withProps({ initial: false, // Set in the installation process in order to create the initial environment. - get environmentAlias() { + get environmentAliases() { const { CmsEnvironmentAlias } = context.models; - return CmsEnvironmentAlias.findOne({ + return CmsEnvironmentAlias.find({ query: { environment: this.id } }); }, get isProduction() { - return this.environmentAlias.then(environmentAlias => { - return environmentAlias && environmentAlias.isProduction === true; + return this.environmentAliases.then(environmentAliases => { + return environmentAliases.some( + environmentAlias => + environmentAlias && environmentAlias.isProduction === true + ); }); } }), @@ -46,18 +49,29 @@ export default ({ createBase, context }: { createBase: Function; context: CmsCon } }, async beforeDelete() { - const environmentAlias = await this.environmentAlias; - if (environmentAlias) { + const environmentAliases = await this.environmentAliases; + const environmentAliasesName = environmentAliases.map( + environmentAlias => environmentAlias.name + ); + + if (environmentAliasesName && environmentAliasesName.length) { throw new Error( - `Cannot delete the environment because it's currently linked to the "${environmentAlias.name}" environment alias.` + `Cannot delete the environment because it's currently linked to the "${environmentAliasesName.join( + ", " + )}" environment aliases.` ); } }, async afterChange() { - const environmentAlias = await this.environmentAlias; - if (environmentAlias) { - environmentAlias.changedOn = new Date(); - await environmentAlias.save(); + const environmentAliases = await this.environmentAliases; + + for (let i = 0; i < environmentAliases.length; i++) { + const environmentAlias = environmentAliases[i]; + + if (environmentAlias) { + environmentAlias.changedOn = new Date(); + await environmentAlias.save(); + } } }, async afterDelete() { From 16442b2cde4ab35a3bb5a621393611a8d498a7fa Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Mon, 29 Jun 2020 12:07:15 +0530 Subject: [PATCH 21/58] test(api-headless-cms): update `environments` test update test to include `environmentAliases` and add `createEnvironmentAlias` helper --- .../__tests__/environments.test.js | 68 ++++++++++++++----- .../src/testing/createEnvironmentAlias.ts | 8 +++ .../api-headless-cms/src/testing/index.ts | 1 + 3 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 packages/api-headless-cms/src/testing/createEnvironmentAlias.ts diff --git a/packages/api-headless-cms/__tests__/environments.test.js b/packages/api-headless-cms/__tests__/environments.test.js index 4707ec78525..ec59ff3fdbf 100644 --- a/packages/api-headless-cms/__tests__/environments.test.js +++ b/packages/api-headless-cms/__tests__/environments.test.js @@ -1,5 +1,6 @@ -import mdbid from "mdbid"; -import { createUtils } from "./utils"; +import useGqlHandler from "./utils/useGqlHandler"; +import { Database } from "@commodo/fields-storage-nedb"; +import { createEnvironment, createEnvironmentAlias } from "@webiny/api-headless-cms/testing"; const CREATE_ENVIRONMENT = /* GraphQL */ ` mutation createEnvironment($data: CmsEnvironmentInput!) { @@ -32,6 +33,11 @@ const GET_ENVIRONMENT = /* GraphQL */ ` id name } + environmentAliases { + id + name + slug + } } } } @@ -49,6 +55,11 @@ const LIST_ENVIRONMENTS = /* GraphQL */ ` id name } + environmentAliases { + id + name + slug + } } } } @@ -56,18 +67,13 @@ const LIST_ENVIRONMENTS = /* GraphQL */ ` `; describe("Environments test", () => { - const { useDatabase, useApolloHandler } = createUtils(); - const { invoke } = useApolloHandler(); - const { getCollection } = useDatabase(); - const initialEnvironment = { id: mdbid() }; + const database = new Database(); + const { invoke } = useGqlHandler({ database }); + + const initial = {}; beforeAll(async () => { - await getCollection("CmsEnvironment").insertOne({ - id: initialEnvironment.id, - name: "Initial Environment", - description: "This is the initial environment.", - createdFrom: null - }); + initial.environment = await createEnvironment({ database }); }); it("should create a new environment", async () => { @@ -92,12 +98,18 @@ describe("Environments test", () => { variables: { data: { name: "new-environment-1", - createdFrom: initialEnvironment.id + createdFrom: initial.environment.id } } } }); + // link `environment with alias` + await createEnvironmentAlias({ + database, + environmentId: body.data.cms.createEnvironment.data.id + }); + expect(body.data.cms.createEnvironment.data.id).toBeTruthy(); expect(body.data.cms.createEnvironment.data.createdFrom.id).toBeTruthy(); }); @@ -108,17 +120,20 @@ describe("Environments test", () => { query: GET_ENVIRONMENT, variables: { where: { - name: "Initial Environment" + name: "new-environment-1" } } } }); expect(body.data.cms.getEnvironment.data.id).toBeTruthy(); - expect(body.data.cms.getEnvironment.data.createdFrom).toBeNull(); + // check for aliases + expect(body.data.cms.getEnvironment.data.environmentAliases).toBeTruthy(); + expect(body.data.cms.getEnvironment.data.environmentAliases.length).toBe(1); + expect(body.data.cms.getEnvironment.data.environmentAliases[0].name).toBeTruthy(); }); - it("should be able to list environments", async () => { + it("should be able to list environments with alias", async () => { let [body] = await invoke({ body: { query: LIST_ENVIRONMENTS @@ -127,18 +142,24 @@ describe("Environments test", () => { const initialListLength = body.data.cms.listEnvironments.data.length; - await invoke({ + const [createdEnvironmentBody] = await invoke({ body: { query: CREATE_ENVIRONMENT, variables: { data: { name: "new-environment-1", - createdFrom: initialEnvironment.id + createdFrom: initial.environment.id } } } }); + // link `environment with alias` + await createEnvironmentAlias({ + database, + environmentId: createdEnvironmentBody.data.cms.createEnvironment.data.id + }); + [body] = await invoke({ body: { query: LIST_ENVIRONMENTS @@ -146,5 +167,16 @@ describe("Environments test", () => { }); expect(body.data.cms.listEnvironments.data.length).toBe(initialListLength + 1); + + expect( + body.data.cms.listEnvironments.data[initialListLength].environmentAliases.length + ).toBe(1); + + expect( + body.data.cms.listEnvironments.data[initialListLength].environmentAliases[0].name + ).toBeTruthy(); + expect( + body.data.cms.listEnvironments.data[initialListLength].environmentAliases[0].slug + ).toBeTruthy(); }); }); diff --git a/packages/api-headless-cms/src/testing/createEnvironmentAlias.ts b/packages/api-headless-cms/src/testing/createEnvironmentAlias.ts new file mode 100644 index 00000000000..d3509f8be2f --- /dev/null +++ b/packages/api-headless-cms/src/testing/createEnvironmentAlias.ts @@ -0,0 +1,8 @@ +export default ({ database, environmentId }) => + database.collection("CmsEnvironmentAlias").insert({ + id: "ea1ea1ea1ea1ea1ea1ea1ea1", + name: "Production", + slug: "production", + description: 'This is the "production" environment alias', + environment: environmentId + }); diff --git a/packages/api-headless-cms/src/testing/index.ts b/packages/api-headless-cms/src/testing/index.ts index 30a5665140b..0c1a192a7d5 100644 --- a/packages/api-headless-cms/src/testing/index.ts +++ b/packages/api-headless-cms/src/testing/index.ts @@ -1,2 +1,3 @@ export { default as createEnvironment } from "./createEnvironment"; export { default as createContentModelGroup } from "./createContentModelGroup"; +export { default as createEnvironmentAlias } from "./createEnvironmentAlias"; \ No newline at end of file From a217870e784d45bce25d5049ec264892ae0d85f3 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Mon, 29 Jun 2020 12:33:20 +0530 Subject: [PATCH 22/58] feat(app-headless-cms): add `environmentAliases` update aliases link and update `listEnvironments` GQL query. --- .../components/EnvironmentSelectorDialog.tsx | 12 ++-- .../src/admin/contexts/Cms/graphql.ts | 2 +- .../Environments/EnvironmentsDataList.tsx | 62 +++++++++++++------ .../src/admin/views/Environments/graphql.ts | 2 +- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentSelectorDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentSelectorDialog.tsx index f5f32557b52..d1ce3dafde6 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentSelectorDialog.tsx +++ b/packages/app-headless-cms/src/admin/components/EnvironmentSelectorDialog.tsx @@ -76,6 +76,13 @@ const NewContentModelDialog: React.FC = ({ {environments.map(item => { const selected = currentEnvironment && item.id === currentEnvironment.id; + let aliases; + if (item.environmentAliases.length) { + aliases = item.environmentAliases + .map(environmentAlias => environmentAlias.name) + .join(", "); + } + return ( = ({ {item.name} - Alias:{" "} - {item.environmentAlias - ? item.environmentAlias.name - : t`None`} + Alias: {aliases ? aliases : t`None`} diff --git a/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts b/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts index a669705d0a9..669341f9b4c 100644 --- a/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts +++ b/packages/app-headless-cms/src/admin/contexts/Cms/graphql.ts @@ -9,7 +9,7 @@ export const LIST_ENVIRONMENTS_SELECTOR_ENVIRONMENTS = gql` id name isProduction - environmentAlias { + environmentAliases { id name isProduction diff --git a/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx b/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx index 22065f086e3..303347ab053 100644 --- a/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx @@ -16,8 +16,24 @@ import { import { Link } from "@webiny/react-router"; import { ConfirmationDialogWithInput } from "./ConfirmationDialogWithInput"; +import styled from "@emotion/styled"; + const t = i18n.ns("app-headless-cms/admin/environments/data-list"); +const Wrapper = styled("div")({ + display: "flex", + alignItems: "baseline", + "& a": { + fontSize: 14, + marginLeft: 8 + } +}); + +const getSeparator = (index, length) => { + const lastIndex = length - 1; + return index < lastIndex ? "," : ""; +}; + const EnvironmentsDataList = () => { const { actions, list } = useCrud(); @@ -58,26 +74,32 @@ const EnvironmentsDataList = () => { {item.default && ( {t`(default)`} )} - - {item.environmentAlias - ? t`Assigned to: {environmentAlias}`({ - environmentAlias: ( - e.stopPropagation()} - to={`/settings/cms/environments/aliases?id=${item.environmentAlias.id}`} - title={t`This environment is linked with the "{environmentAlias}" alias.`( - { - environmentAlias: - item.environmentAlias.name - } - )} - > - {item.environmentAlias.name} - - ) - }) - : t`Not linked with an alias.`} - + + + {item.environmentAliases && + item.environmentAliases.length + ? t`Assigned to:` + : t`Not linked with an alias.`} + + {item.environmentAliases && + item.environmentAliases.map((envAlias, index) => ( + e.stopPropagation()} + to={`/settings/cms/environments/aliases?id=${envAlias.id}`} + title={t`This environment is linked with the "{environmentAlias}" alias.`( + { + environmentAlias: envAlias.name + } + )} + > + {envAlias.name} + {getSeparator( + index, + item.environmentAliases.length + )} + + ))} + diff --git a/packages/app-headless-cms/src/admin/views/Environments/graphql.ts b/packages/app-headless-cms/src/admin/views/Environments/graphql.ts index 1100df621d3..39808b15201 100644 --- a/packages/app-headless-cms/src/admin/views/Environments/graphql.ts +++ b/packages/app-headless-cms/src/admin/views/Environments/graphql.ts @@ -33,7 +33,7 @@ export const LIST_ENVIRONMENTS = gql` name isProduction createdOn - environmentAlias { + environmentAliases { id name } From 752753b8580d8cfeb4e1b3f436831a6cd7a232ee Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Tue, 30 Jun 2020 11:06:17 +0200 Subject: [PATCH 23/58] fix: make sure newly added ref fields cannot reference a model with no title field --- .../mocks/fields/refInvalidReferences.js | 98 +++++++++++++++++++ .../refFieldInvalidReferences.test.js | 50 ++++++++++ .../src/content/plugins/modelFields/ref.ts | 43 +++++++- 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 packages/api-headless-cms/__tests__/mocks/fields/refInvalidReferences.js create mode 100644 packages/api-headless-cms/__tests__/refFieldInvalidReferences.test.js diff --git a/packages/api-headless-cms/__tests__/mocks/fields/refInvalidReferences.js b/packages/api-headless-cms/__tests__/mocks/fields/refInvalidReferences.js new file mode 100644 index 00000000000..45a52953849 --- /dev/null +++ b/packages/api-headless-cms/__tests__/mocks/fields/refInvalidReferences.js @@ -0,0 +1,98 @@ +import { locales } from "@webiny/api-i18n/testing"; + +const mocks = { + bookContentModel: ({ contentModelGroupId }) => ({ + name: "Book", + group: contentModelGroupId, + fields: [] + }), + authorContentModel: ({ contentModelGroupId }) => ({ + name: "Author", + group: contentModelGroupId, + fields: [ + { + _id: "vqk-UApa0", + fieldId: "title", + type: "text", + label: { + values: [ + { + locale: locales.en.id, + value: "Title" + }, + { + locale: locales.de.id, + value: "Titel" + } + ] + } + }, + { + _id: "id-favorite-book", + fieldId: "favoriteBook", + type: "ref", + settings: { + modelId: "book" + }, + label: { + values: [ + { + locale: locales.en.id, + value: "Favorite Book" + }, + { + locale: locales.de.id, + value: "Lieblingsbuch" + } + ] + } + }, + { + _id: "id-other-books", + fieldId: "otherBooks", + type: "ref", + multipleValues: true, + settings: { + modelId: "book" + }, + label: { + values: [ + { + locale: locales.en.id, + value: "Other Books" + }, + { + locale: locales.de.id, + value: "Andere Bücher" + } + ] + } + } + ] + }), + authorWithoutBookRefFields: ({ contentModelGroupId }) => ({ + name: "Author", + group: contentModelGroupId, + fields: [ + { + _id: "vqk-UApa0", + fieldId: "title", + type: "text", + label: { + values: [ + { + locale: locales.en.id, + value: "Title" + }, + { + locale: locales.de.id, + value: "Titel" + } + ] + } + } + ] + }) +}; + +export default mocks; diff --git a/packages/api-headless-cms/__tests__/refFieldInvalidReferences.test.js b/packages/api-headless-cms/__tests__/refFieldInvalidReferences.test.js new file mode 100644 index 00000000000..228094056b8 --- /dev/null +++ b/packages/api-headless-cms/__tests__/refFieldInvalidReferences.test.js @@ -0,0 +1,50 @@ +import useContentHandler from "./utils/useContentHandler"; +import refMocks from "./mocks/fields/refInvalidReferences"; +import { createContentModelGroup, createEnvironment } from "@webiny/api-headless-cms/testing"; + +describe("Ref Field - Invalid References In Test", () => { + const { database, environment } = useContentHandler(); + const initial = {}; + + beforeAll(async () => { + // Let's create a basic environment and a content model group. + initial.environment = await createEnvironment({ database }); + initial.contentModelGroup = await createContentModelGroup({ database }); + }); + + it(`should not allow selection of a ref model that doesn't have a title field`, async () => { + const { createContentModel } = environment(initial.environment.id); + + await createContentModel({ + data: refMocks.bookContentModel({ contentModelGroupId: initial.contentModelGroup.id }) + }); + + let error; + try { + await createContentModel({ + data: refMocks.authorContentModel({ + contentModelGroupId: initial.contentModelGroup.id + }) + }); + } catch (e) { + error = e; + } + + expect(error.message).toBe( + `Cannot save content model because the ref field "favoriteBook" references a content model (book) that has no title field assigned.` + ); + + error = null; + try { + await createContentModel({ + data: refMocks.authorWithoutBookRefFields({ + contentModelGroupId: initial.contentModelGroup.id + }) + }); + } catch (e) { + error = e; + } + + expect(error).toBe(null); + }); +}); diff --git a/packages/api-headless-cms/src/content/plugins/modelFields/ref.ts b/packages/api-headless-cms/src/content/plugins/modelFields/ref.ts index 4fba9a67f7b..9cb1dd2eb83 100644 --- a/packages/api-headless-cms/src/content/plugins/modelFields/ref.ts +++ b/packages/api-headless-cms/src/content/plugins/modelFields/ref.ts @@ -200,4 +200,45 @@ const lockedFieldPlugin: CmsModelLockedFieldPlugin = { } }; -export default [plugin, lockedFieldPlugin]; +const checkRefFieldsBeforeSave = { + name: "context-cms-model-ref-field-check-referenced-model", + type: "context", + apply(context) { + const { CmsContentModel } = context.models; + withHooks({ + async beforeSave() { + const refFields = this.fields.filter(field => { + if (field.type !== "ref") { + return false; + } + + const isLockedField = this.lockedFields.find( + item => item.fieldId === field.fieldId + ); + return !isLockedField; + }); + + // Now that we have non-locked "ref" fields, let's check if the actual model that is referenced + // is ready to be selected. In other words, we don't want to allow models without a title field, + // because basically, all search inputs in the UI will stop working. And not only that, with this + // check, we ensure that the referenced model contains at least one field. Otherwise, the GraphQL + // schema that would be generated after saving this content model, would be invalid, and the + // GraphQL server wouldn't be able to start. + for (let i = 0; i < refFields.length; i++) { + const refField = refFields[i]; + const contentModel = await CmsContentModel.findOne({ + modelId: refField.settings.modelId + }); + + if (!contentModel.titleFieldId) { + throw new Error( + `Cannot save content model because the ref field "${refField.fieldId}" references a content model (${refField.settings.modelId}) that has no title field assigned.` + ); + } + } + } + })(CmsContentModel); + } +}; + +export default [plugin, lockedFieldPlugin, checkRefFieldsBeforeSave]; From 485a05c2f1f93c4d9c455428c863de028b9b3b73 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 19 Jun 2020 08:47:36 +0530 Subject: [PATCH 24/58] feat(app-page-builder): remove `domain` input set a default domain --- .../src/admin/plugins/install.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/app-page-builder/src/admin/plugins/install.tsx b/packages/app-page-builder/src/admin/plugins/install.tsx index 9fefa62a7f5..c44562c49d0 100644 --- a/packages/app-page-builder/src/admin/plugins/install.tsx +++ b/packages/app-page-builder/src/admin/plugins/install.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from "react"; +import React, { useState, useCallback, useMemo } from "react"; import gql from "graphql-tag"; import { useApolloClient } from "react-apollo"; import { i18n } from "@webiny/app/i18n"; @@ -55,6 +55,13 @@ const installationSteps = { 5: t`Finalizing...` }; +const getCurrentDomain = () => { + const isLocalHost = window.location.origin.includes("localhost"); + const localDomain = "http://localhost:3000"; + + return isLocalHost ? localDomain : window.location.origin; +}; + const PBInstaller = ({ onInstalled }) => { const client = useApolloClient(); const [loading, setLoading] = useState(false); @@ -92,7 +99,7 @@ const PBInstaller = ({ onInstalled }) => { ); return ( -
    + {({ Bind, submit }) => ( {loading && } @@ -116,14 +123,6 @@ const PBInstaller = ({ onInstalled }) => { /> - - - - - From 03b438be42412bdf2e55ebd234db4d23535aa4b5 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 19 Jun 2020 08:49:49 +0530 Subject: [PATCH 25/58] feat(app-page-builder): add `configure domain` confirmation add `configure domain` confirmation before page preview --- .../pageOptionsMenu/PageOptionsMenu.tsx | 21 +++++++++- .../pageDetails/pageRevisions/Revision.tsx | 22 +++++++++- .../components/PreviewPageButton.tsx | 41 ++++++++++++++----- .../src/utils/configureDomain.tsx | 25 +++++++++++ 4 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 packages/app-page-builder/src/utils/configureDomain.tsx diff --git a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx index d89e7f885b2..ee648c14f8f 100644 --- a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx +++ b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx @@ -15,6 +15,10 @@ import { setHomePage } from "./graphql"; import { useConfirmationDialog } from "@webiny/app-admin/hooks/useConfirmationDialog"; import { getPlugins } from "@webiny/plugins"; import { PbPageDetailsHeaderRightOptionsMenuItemPlugin } from "@webiny/app-page-builder/types"; +import { + ConfigureDomainMessage, + configureDomainTitle +} from "@webiny/app-page-builder/utils/configureDomain"; const menuStyles = css({ width: 250, @@ -31,7 +35,7 @@ const PageOptionsMenu = props => { pageDetails: { page } } = props; - const { getPageUrl, getPagePreviewUrl } = usePageBuilderSettings(); + const { getPageUrl, getPagePreviewUrl, getDomain, isSiteRunning } = usePageBuilderSettings(); const { showSnackbar } = useSnackbar(); const { showConfirmation } = useConfirmationDialog({ title: "Delete page", @@ -44,6 +48,11 @@ const PageOptionsMenu = props => { ) }); + const { showConfirmation: showSettingsConfirmation } = useConfirmationDialog({ + title: configureDomainTitle, + message: + }); + // We must prevent opening in new tab - Cypress doesn't work with new tabs. const target = window.Cypress ? "_self" : "_blank"; @@ -68,7 +77,15 @@ const PageOptionsMenu = props => { View ) : ( - window.open(getPagePreviewUrl(page), target)}> + { + if (isSiteRunning) { + window.open(getPagePreviewUrl(page), target); + } else { + showSettingsConfirmation(null); + } + }} + > { const Revision = ({ rev }: RevisionProps) => { const { icon, text: tooltipText } = getIcon(rev); - const { getPagePreviewUrl } = usePageBuilderSettings(); + const { getPagePreviewUrl, getDomain, isSiteRunning } = usePageBuilderSettings(); const { deleteRevision, createRevision, publishRevision, editRevision } = useRevisionHandlers({ rev }); + const { showConfirmation: showPreviewConfirmation } = useConfirmationDialog({ + title: configureDomainTitle, + message: + }); + return ( { )} - window.open(getPagePreviewUrl(rev), "_blank")}> + { + if (isSiteRunning) { + window.open(getPagePreviewUrl(rev), "_blank"); + } else { + showPreviewConfirmation(null); + } + }} + > } /> diff --git a/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx b/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx index 167c463c222..668724f3c6c 100644 --- a/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx +++ b/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx @@ -1,27 +1,48 @@ import React from "react"; import { connect } from "@webiny/app-page-builder/editor/redux"; -import { getPage } from "@webiny/app-page-builder/editor/selectors"; import { omit, isEqual } from "lodash"; +import { getPage } from "@webiny/app-page-builder/editor/selectors"; import { MenuItem } from "@webiny/ui/Menu"; import { usePageBuilderSettings } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; import { ListItemGraphic } from "@webiny/ui/List"; import { Icon } from "@webiny/ui/Icon"; +import { ConfirmationDialog } from "@webiny/ui/ConfirmationDialog"; import { ReactComponent as PreviewIcon } from "@webiny/app-page-builder/admin/assets/visibility.svg"; +import { + ConfigureDomainMessage, + configureDomainTitle +} from "@webiny/app-page-builder/utils/configureDomain"; const openTarget = window.Cypress ? "_self" : "_blank"; const PreviewPageButton = ({ page }) => { - const { getPagePreviewUrl } = usePageBuilderSettings(); + const { getPagePreviewUrl, getDomain, isSiteRunning } = usePageBuilderSettings(); + return ( - window.open(getPagePreviewUrl(page), openTarget)} - data-testid={"pb-editor-page-options-menu-preview"} + } > - - } /> - - Preview - + {({ showConfirmation }) => { + return ( + { + if (isSiteRunning) { + window.open(getPagePreviewUrl(page), openTarget); + } else { + showConfirmation(); + } + }} + data-testid={"pb-editor-page-options-menu-preview"} + > + + } /> + + Preview + + ); + }} + ); }; diff --git a/packages/app-page-builder/src/utils/configureDomain.tsx b/packages/app-page-builder/src/utils/configureDomain.tsx new file mode 100644 index 00000000000..f008b20dbaa --- /dev/null +++ b/packages/app-page-builder/src/utils/configureDomain.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { css } from "emotion"; +import {i18n} from "@webiny/app/i18n"; + +const t = i18n.ns("app-page-builder/utils"); + +const confirmationMessageStyles = css({ + "& code": { + backgroundColor: "var(--mdc-theme-background)", + padding: "0px 8px" + } +}); + +export const configureDomainTitle = t`Configure domain`; + +export const ConfigureDomainMessage = ({ domain }) => ( + + No site is running at {domain} +
    + Either start the server by cd apps/site && yarn start +
    + or update the domain by going into{" "} +
    page builder settings + +); From e9b98f4ae8cd23240b869b23f06df5892d8208da Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 19 Jun 2020 09:26:37 +0530 Subject: [PATCH 26/58] feat(app-page-builder): add `useSiteStatus` hook --- .../usePageBuilderSettings.ts | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts index 826b66008cf..41146541712 100644 --- a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts +++ b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts @@ -1,9 +1,35 @@ -import { useCallback } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useQuery } from "react-apollo"; import gql from "graphql-tag"; import { get } from "lodash"; import getPagePreviewUrlFunction from "./getPagePreviewUrl"; +function useSiteStatus(url) { + const [active, setActive] = useState(true); + + useEffect(() => { + async function getResponse(url) { + let response = null; + try { + response = await fetch(url, { + method: "GET", + mode: "no-cors" + }); + setActive(response); + } catch (e) { + console.error(e); + setActive(false); + } + } + + if (url && url.includes("localhost")) { + getResponse(url).then(() => console.log(`Done!!`)); + } + }, [url]); + + return active; +} + export const DOMAIN_QUERY = gql` query PbGetDomain { pageBuilder { @@ -24,6 +50,8 @@ export function usePageBuilderSettings() { return get(data, "pageBuilder.getSettings.data.domain"); }; + const isSiteRunning = useSiteStatus(getDomain()); + const getPageUrl = useCallback( page => { if (loading) { @@ -45,6 +73,7 @@ export function usePageBuilderSettings() { ); return { + isSiteRunning, getDomain, getPageUrl, getPagePreviewUrl, From 00e2cee90a2e3a9315760a195af76c8b7c509109 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 26 Jun 2020 07:54:09 +0530 Subject: [PATCH 27/58] feat(app-page-builder): add `DialogContainer` --- packages/app-page-builder/src/admin/views/Pages/Editor.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/app-page-builder/src/admin/views/Pages/Editor.tsx b/packages/app-page-builder/src/admin/views/Pages/Editor.tsx index 00612563efb..7d97b1619ec 100644 --- a/packages/app-page-builder/src/admin/views/Pages/Editor.tsx +++ b/packages/app-page-builder/src/admin/views/Pages/Editor.tsx @@ -11,7 +11,7 @@ import { GET_PAGE } from "@webiny/app-page-builder/admin/graphql/pages"; import { useSavedElements } from "@webiny/app-page-builder/admin/hooks/useSavedElements"; import Snackbar from "@webiny/app-admin/plugins/Snackbar/Snackbar"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; - +import { DialogContainer } from "@webiny/app-admin/plugins/Dialog/Dialog"; import { Typography } from "@webiny/ui/Typography"; import { LoadingEditor, LoadingTitle } from "./EditorStyled.js"; import editorMock from "@webiny/app-page-builder/admin/assets/editor-mock.png"; @@ -46,7 +46,7 @@ const Editor = () => { if (loading || !ready) { return ( - + {"page Loading Editor. @@ -90,6 +90,9 @@ const Editor = () => {
    +
    + +
    ); }, From d740954f3cf6e25a1cce0a4408e315600f30bbf9 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 26 Jun 2020 07:54:57 +0530 Subject: [PATCH 28/58] feat(app-page-builder): add `useConfigureDomainDialog` hook --- .../src/utils/configureDomain.tsx | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/packages/app-page-builder/src/utils/configureDomain.tsx b/packages/app-page-builder/src/utils/configureDomain.tsx index f008b20dbaa..566be53feca 100644 --- a/packages/app-page-builder/src/utils/configureDomain.tsx +++ b/packages/app-page-builder/src/utils/configureDomain.tsx @@ -1,6 +1,7 @@ import React from "react"; import { css } from "emotion"; -import {i18n} from "@webiny/app/i18n"; +import { i18n } from "@webiny/app/i18n"; +import { useDialog } from "@webiny/app-admin/hooks/useDialog"; const t = i18n.ns("app-page-builder/utils"); @@ -13,13 +14,40 @@ const confirmationMessageStyles = css({ export const configureDomainTitle = t`Configure domain`; -export const ConfigureDomainMessage = ({ domain }) => ( - - No site is running at {domain} -
    - Either start the server by cd apps/site && yarn start -
    - or update the domain by going into{" "} - page builder settings -
    -); +export const ConfigureDomainMessage = ({ domain }) => { + const isLocalHost = domain && domain.includes("localhost"); + return ( + + No site is running at {domain} +
    + {isLocalHost ? ( + + Either start the server by cd apps/site && yarn start{" "} + + ) : ( + + Either deploy the site by running yarn webiny deploy apps{" "} + + )} +
    + or update the domain by going into{" "} + page builder settings +
    + ); +}; + +export const useConfigureDomainDialog = (domain, onAccept = null) => { + const { showDialog } = useDialog(); + + return { + showConfigureDomainDialog: () => { + showDialog(, { + title: configureDomainTitle, + actions: { + accept: { label: "Refresh", onClick: onAccept }, + cancel: { label: "Cancel" } + } + }); + } + }; +}; From d671577f21dc2d77d05384843b27b8347b3ef03c Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 26 Jun 2020 11:58:43 +0530 Subject: [PATCH 29/58] refactor(app-page-builder): use `useConfigureDomainDialog` get site status from `useSiteStatus` instead of `usePageBuilderSettings` --- .../src/admin/plugins/install.tsx | 2 +- .../pageOptionsMenu/PageOptionsMenu.tsx | 41 +++++++------- .../pageDetails/pageRevisions/Revision.tsx | 24 ++++----- .../components/PreviewPageButton.tsx | 53 ++++++++----------- 4 files changed, 56 insertions(+), 64 deletions(-) diff --git a/packages/app-page-builder/src/admin/plugins/install.tsx b/packages/app-page-builder/src/admin/plugins/install.tsx index c44562c49d0..927d9c14292 100644 --- a/packages/app-page-builder/src/admin/plugins/install.tsx +++ b/packages/app-page-builder/src/admin/plugins/install.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useMemo } from "react"; +import React, { useState, useCallback } from "react"; import gql from "graphql-tag"; import { useApolloClient } from "react-apollo"; import { i18n } from "@webiny/app/i18n"; diff --git a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx index ee648c14f8f..a891b1fea99 100644 --- a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx +++ b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx @@ -6,7 +6,10 @@ import { ReactComponent as PreviewIcon } from "@webiny/app-page-builder/admin/as import { ReactComponent as HomeIcon } from "@webiny/app-page-builder/admin/assets/round-home-24px.svg"; import { ListItemGraphic } from "@webiny/ui/List"; import { MenuItem, Menu } from "@webiny/ui/Menu"; -import { usePageBuilderSettings } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import { + usePageBuilderSettings, + useSiteStatus +} from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; import { css } from "emotion"; import { Mutation } from "react-apollo"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; @@ -15,10 +18,7 @@ import { setHomePage } from "./graphql"; import { useConfirmationDialog } from "@webiny/app-admin/hooks/useConfirmationDialog"; import { getPlugins } from "@webiny/plugins"; import { PbPageDetailsHeaderRightOptionsMenuItemPlugin } from "@webiny/app-page-builder/types"; -import { - ConfigureDomainMessage, - configureDomainTitle -} from "@webiny/app-page-builder/utils/configureDomain"; +import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/configureDomain"; const menuStyles = css({ width: 250, @@ -35,7 +35,9 @@ const PageOptionsMenu = props => { pageDetails: { page } } = props; - const { getPageUrl, getPagePreviewUrl, getDomain, isSiteRunning } = usePageBuilderSettings(); + const { getPageUrl, getPagePreviewUrl, getDomain } = usePageBuilderSettings(); + const [isSiteRunning, refreshSiteStatus] = useSiteStatus(getDomain()); + const { showSnackbar } = useSnackbar(); const { showConfirmation } = useConfirmationDialog({ title: "Delete page", @@ -48,14 +50,21 @@ const PageOptionsMenu = props => { ) }); - const { showConfirmation: showSettingsConfirmation } = useConfirmationDialog({ - title: configureDomainTitle, - message: - }); + const { showConfigureDomainDialog } = useConfigureDomainDialog(getDomain(), refreshSiteStatus); // We must prevent opening in new tab - Cypress doesn't work with new tabs. const target = window.Cypress ? "_self" : "_blank"; + const handlePreviewClick = () => { + const url = page.locked ? getPageUrl(page) : getPagePreviewUrl(page); + + if (isSiteRunning) { + window.open(url, target, "noopener"); + } else { + showConfigureDomainDialog(); + } + }; + return ( { } > {page.locked ? ( - window.open(getPageUrl(page), target)}> + { View ) : ( - { - if (isSiteRunning) { - window.open(getPagePreviewUrl(page), target); - } else { - showSettingsConfirmation(null); - } - }} - > + { const Revision = ({ rev }: RevisionProps) => { const { icon, text: tooltipText } = getIcon(rev); - const { getPagePreviewUrl, getDomain, isSiteRunning } = usePageBuilderSettings(); + const { getDomain, getPagePreviewUrl } = usePageBuilderSettings(); + const [isSiteRunning, refreshSiteStatus] = useSiteStatus(getDomain()); + const { deleteRevision, createRevision, publishRevision, editRevision } = useRevisionHandlers({ rev }); - const { showConfirmation: showPreviewConfirmation } = useConfirmationDialog({ - title: configureDomainTitle, - message: - }); + const { showConfigureDomainDialog } = useConfigureDomainDialog(getDomain(), refreshSiteStatus); return ( { { if (isSiteRunning) { - window.open(getPagePreviewUrl(rev), "_blank"); + window.open(getPagePreviewUrl(rev), "_blank", "noopener"); } else { - showPreviewConfirmation(null); + showConfigureDomainDialog(); } }} > diff --git a/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx b/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx index 668724f3c6c..4a41b0a571a 100644 --- a/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx +++ b/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx @@ -3,46 +3,39 @@ import { connect } from "@webiny/app-page-builder/editor/redux"; import { omit, isEqual } from "lodash"; import { getPage } from "@webiny/app-page-builder/editor/selectors"; import { MenuItem } from "@webiny/ui/Menu"; -import { usePageBuilderSettings } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import { + usePageBuilderSettings, + useSiteStatus +} from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; import { ListItemGraphic } from "@webiny/ui/List"; import { Icon } from "@webiny/ui/Icon"; -import { ConfirmationDialog } from "@webiny/ui/ConfirmationDialog"; import { ReactComponent as PreviewIcon } from "@webiny/app-page-builder/admin/assets/visibility.svg"; -import { - ConfigureDomainMessage, - configureDomainTitle -} from "@webiny/app-page-builder/utils/configureDomain"; +import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/configureDomain"; const openTarget = window.Cypress ? "_self" : "_blank"; const PreviewPageButton = ({ page }) => { - const { getPagePreviewUrl, getDomain, isSiteRunning } = usePageBuilderSettings(); + const { getPagePreviewUrl, getDomain } = usePageBuilderSettings(); + const [isSiteRunning, refreshSiteStatus] = useSiteStatus(getDomain()); + + const { showConfigureDomainDialog } = useConfigureDomainDialog(getDomain(), refreshSiteStatus); return ( - } - > - {({ showConfirmation }) => { - return ( - { - if (isSiteRunning) { - window.open(getPagePreviewUrl(page), openTarget); - } else { - showConfirmation(); - } - }} - data-testid={"pb-editor-page-options-menu-preview"} - > - - } /> - - Preview - - ); + { + if (isSiteRunning) { + window.open(getPagePreviewUrl(page), openTarget, "noopener"); + } else { + showConfigureDomainDialog(); + } }} - + data-testid={"pb-editor-page-options-menu-preview"} + > + + } /> + + Preview + ); }; From 6043926b103416fe890d1b769e0cc8a55aad23c9 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 26 Jun 2020 11:59:51 +0530 Subject: [PATCH 30/58] feat(app-page-builder): add `useSiteStatus` add helpers like `pingSite` and `fetchWithCache` in utils --- .../hooks/usePageBuilderSettings/index.ts | 1 + .../usePageBuilderSettingsUtils.ts | 48 +++++++++++++++++++ .../usePageBuilderSettings/useSiteStatus.ts | 19 ++++++++ 3 files changed, 68 insertions(+) create mode 100644 packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils.ts create mode 100644 packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/useSiteStatus.ts diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts index b8286a7fa8a..9340991b2e2 100644 --- a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts +++ b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts @@ -1 +1,2 @@ export { usePageBuilderSettings } from "./usePageBuilderSettings"; +export { useSiteStatus } from "./useSiteStatus"; diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils.ts b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils.ts new file mode 100644 index 00000000000..aabbe4b2013 --- /dev/null +++ b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils.ts @@ -0,0 +1,48 @@ +const MINUTE = 60 * 1000; +// Plain in-memory store for fetch response +const CACHE = {}; + +function getCacheTimer(time) { + let cacheTimer = 0; + const now = new Date().getTime(); + if (cacheTimer < now + time) { + cacheTimer = now + time; + } + return cacheTimer; +} + +export async function fetchWithCache(params: { url: string; time?: number; byPassCache: boolean }) { + const { url, time = 5 * MINUTE, byPassCache } = params; + + const now = new Date().getTime(); + + if (!CACHE[url] || CACHE[url].cacheTimer < now || byPassCache) { + try { + const response = await fetch(url, { + method: "GET", + mode: "no-cors" + }); + // Update cache with response + if (response) { + CACHE[url] = { ...response, active: true }; + CACHE[url].cacheTimer = getCacheTimer(time); + } + } catch (e) { + CACHE[url] = { active: false }; + } + } + + return CACHE[url]; +} + +export const pingSite = async (params: { url: string; cb: any; byPassCache: boolean }) => { + const { url, cb, byPassCache } = params; + try { + const response = await fetchWithCache({ url, byPassCache }); + + cb(response && response.active); + } catch (e) { + console.error(e); + cb(false); + } +}; diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/useSiteStatus.ts b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/useSiteStatus.ts new file mode 100644 index 00000000000..e13d1776713 --- /dev/null +++ b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/useSiteStatus.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; +import { pingSite } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils"; + +export const useSiteStatus = url => { + const [active, setActive] = useState(true); + + useEffect(() => { + if (url) { + pingSite({ url, cb: setActive, byPassCache: false }).then(); + } + }, [url]); + + return [ + active, + () => { + pingSite({ url, cb: setActive, byPassCache: true }).then(); + } + ]; +}; From 674d5ae1435146332e50fd5bc23763173e3aeaed Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Fri, 26 Jun 2020 12:04:03 +0530 Subject: [PATCH 31/58] refactor(app-page-builder): remove `useSiteStatus` remove `useSiteStatus` from `usePageBuilderSettings` --- .../usePageBuilderSettings.ts | 31 +------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts index 41146541712..826b66008cf 100644 --- a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts +++ b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettings.ts @@ -1,35 +1,9 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback } from "react"; import { useQuery } from "react-apollo"; import gql from "graphql-tag"; import { get } from "lodash"; import getPagePreviewUrlFunction from "./getPagePreviewUrl"; -function useSiteStatus(url) { - const [active, setActive] = useState(true); - - useEffect(() => { - async function getResponse(url) { - let response = null; - try { - response = await fetch(url, { - method: "GET", - mode: "no-cors" - }); - setActive(response); - } catch (e) { - console.error(e); - setActive(false); - } - } - - if (url && url.includes("localhost")) { - getResponse(url).then(() => console.log(`Done!!`)); - } - }, [url]); - - return active; -} - export const DOMAIN_QUERY = gql` query PbGetDomain { pageBuilder { @@ -50,8 +24,6 @@ export function usePageBuilderSettings() { return get(data, "pageBuilder.getSettings.data.domain"); }; - const isSiteRunning = useSiteStatus(getDomain()); - const getPageUrl = useCallback( page => { if (loading) { @@ -73,7 +45,6 @@ export function usePageBuilderSettings() { ); return { - isSiteRunning, getDomain, getPageUrl, getPagePreviewUrl, From acc0adfbe5d5284bba88727737f28cd8869e9676 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Mon, 29 Jun 2020 18:47:27 +0530 Subject: [PATCH 32/58] refactor(app-page-builder): move `useSiteStatus` into a separate file update imports. rename `bypassCache` to `ignoreCache` --- .../src/admin/hooks/usePageBuilderSettings/index.ts | 1 - .../src/admin/hooks/useSiteStatus/index.ts | 1 + .../useSiteStatus.ts | 6 +++--- .../utils.ts} | 12 ++++++------ .../header/pageOptionsMenu/PageOptionsMenu.tsx | 8 +++----- .../plugins/pageDetails/pageRevisions/Revision.tsx | 8 +++----- .../defaultBar/components/PreviewPageButton.tsx | 8 +++----- 7 files changed, 19 insertions(+), 25 deletions(-) create mode 100644 packages/app-page-builder/src/admin/hooks/useSiteStatus/index.ts rename packages/app-page-builder/src/admin/hooks/{usePageBuilderSettings => useSiteStatus}/useSiteStatus.ts (50%) rename packages/app-page-builder/src/admin/hooks/{usePageBuilderSettings/usePageBuilderSettingsUtils.ts => useSiteStatus/utils.ts} (72%) diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts index 9340991b2e2..b8286a7fa8a 100644 --- a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts +++ b/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/index.ts @@ -1,2 +1 @@ export { usePageBuilderSettings } from "./usePageBuilderSettings"; -export { useSiteStatus } from "./useSiteStatus"; diff --git a/packages/app-page-builder/src/admin/hooks/useSiteStatus/index.ts b/packages/app-page-builder/src/admin/hooks/useSiteStatus/index.ts new file mode 100644 index 00000000000..0d9f17e5e99 --- /dev/null +++ b/packages/app-page-builder/src/admin/hooks/useSiteStatus/index.ts @@ -0,0 +1 @@ +export { useSiteStatus } from "./useSiteStatus"; \ No newline at end of file diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/useSiteStatus.ts b/packages/app-page-builder/src/admin/hooks/useSiteStatus/useSiteStatus.ts similarity index 50% rename from packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/useSiteStatus.ts rename to packages/app-page-builder/src/admin/hooks/useSiteStatus/useSiteStatus.ts index e13d1776713..c7a7f09698a 100644 --- a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/useSiteStatus.ts +++ b/packages/app-page-builder/src/admin/hooks/useSiteStatus/useSiteStatus.ts @@ -1,19 +1,19 @@ import { useEffect, useState } from "react"; -import { pingSite } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils"; +import { pingSite } from "./utils"; export const useSiteStatus = url => { const [active, setActive] = useState(true); useEffect(() => { if (url) { - pingSite({ url, cb: setActive, byPassCache: false }).then(); + pingSite({ url, cb: setActive, ignoreCache: false }); } }, [url]); return [ active, () => { - pingSite({ url, cb: setActive, byPassCache: true }).then(); + pingSite({ url, cb: setActive, ignoreCache: true }); } ]; }; diff --git a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils.ts b/packages/app-page-builder/src/admin/hooks/useSiteStatus/utils.ts similarity index 72% rename from packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils.ts rename to packages/app-page-builder/src/admin/hooks/useSiteStatus/utils.ts index aabbe4b2013..2fb7dc22648 100644 --- a/packages/app-page-builder/src/admin/hooks/usePageBuilderSettings/usePageBuilderSettingsUtils.ts +++ b/packages/app-page-builder/src/admin/hooks/useSiteStatus/utils.ts @@ -11,12 +11,12 @@ function getCacheTimer(time) { return cacheTimer; } -export async function fetchWithCache(params: { url: string; time?: number; byPassCache: boolean }) { - const { url, time = 5 * MINUTE, byPassCache } = params; +export async function fetchWithCache(params: { url: string; time?: number; ignoreCache: boolean }) { + const { url, time = 5 * MINUTE, ignoreCache } = params; const now = new Date().getTime(); - if (!CACHE[url] || CACHE[url].cacheTimer < now || byPassCache) { + if (!CACHE[url] || CACHE[url].cacheTimer < now || ignoreCache) { try { const response = await fetch(url, { method: "GET", @@ -35,10 +35,10 @@ export async function fetchWithCache(params: { url: string; time?: number; byPas return CACHE[url]; } -export const pingSite = async (params: { url: string; cb: any; byPassCache: boolean }) => { - const { url, cb, byPassCache } = params; +export const pingSite = async (params: { url: string; cb: any; ignoreCache: boolean }) => { + const { url, cb, ignoreCache } = params; try { - const response = await fetchWithCache({ url, byPassCache }); + const response = await fetchWithCache({ url, ignoreCache }); cb(response && response.active); } catch (e) { diff --git a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx index a891b1fea99..1d392ca470f 100644 --- a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx +++ b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx @@ -6,10 +6,8 @@ import { ReactComponent as PreviewIcon } from "@webiny/app-page-builder/admin/as import { ReactComponent as HomeIcon } from "@webiny/app-page-builder/admin/assets/round-home-24px.svg"; import { ListItemGraphic } from "@webiny/ui/List"; import { MenuItem, Menu } from "@webiny/ui/Menu"; -import { - usePageBuilderSettings, - useSiteStatus -} from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import { usePageBuilderSettings } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import { useSiteStatus } from "@webiny/app-page-builder/admin/hooks/useSiteStatus"; import { css } from "emotion"; import { Mutation } from "react-apollo"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; @@ -18,7 +16,7 @@ import { setHomePage } from "./graphql"; import { useConfirmationDialog } from "@webiny/app-admin/hooks/useConfirmationDialog"; import { getPlugins } from "@webiny/plugins"; import { PbPageDetailsHeaderRightOptionsMenuItemPlugin } from "@webiny/app-page-builder/types"; -import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/configureDomain"; +import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/useConfigureDomain"; const menuStyles = css({ width: 250, diff --git a/packages/app-page-builder/src/admin/plugins/pageDetails/pageRevisions/Revision.tsx b/packages/app-page-builder/src/admin/plugins/pageDetails/pageRevisions/Revision.tsx index 104756d0f76..9f26ae95540 100644 --- a/packages/app-page-builder/src/admin/plugins/pageDetails/pageRevisions/Revision.tsx +++ b/packages/app-page-builder/src/admin/plugins/pageDetails/pageRevisions/Revision.tsx @@ -19,17 +19,15 @@ import { ReactComponent as LockIcon } from "@webiny/app-page-builder/admin/asset import { ReactComponent as BeenHereIcon } from "@webiny/app-page-builder/admin/assets/beenhere.svg"; import { ReactComponent as GestureIcon } from "@webiny/app-page-builder/admin/assets/gesture.svg"; import { useRevisionHandlers } from "./useRevisionHandlers"; -import { - usePageBuilderSettings, - useSiteStatus -} from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import { usePageBuilderSettings } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import {useSiteStatus} from "@webiny/app-page-builder/admin/hooks/useSiteStatus"; import { ReactComponent as AddIcon } from "@webiny/app-page-builder/admin/assets/add.svg"; import { ReactComponent as EditIcon } from "@webiny/app-page-builder/admin/assets/edit.svg"; import { ReactComponent as PublishIcon } from "@webiny/app-page-builder/admin/assets/round-publish-24px.svg"; import { ReactComponent as DeleteIcon } from "@webiny/app-page-builder/admin/assets/delete.svg"; import { ReactComponent as PreviewIcon } from "@webiny/app-page-builder/admin/assets/visibility.svg"; import { PbPageRevision } from "@webiny/app-page-builder/types"; -import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/configureDomain"; +import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/useConfigureDomain"; type RevisionProps = { rev: PbPageRevision; diff --git a/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx b/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx index 4a41b0a571a..2274498d572 100644 --- a/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx +++ b/packages/app-page-builder/src/editor/plugins/defaultBar/components/PreviewPageButton.tsx @@ -3,14 +3,12 @@ import { connect } from "@webiny/app-page-builder/editor/redux"; import { omit, isEqual } from "lodash"; import { getPage } from "@webiny/app-page-builder/editor/selectors"; import { MenuItem } from "@webiny/ui/Menu"; -import { - usePageBuilderSettings, - useSiteStatus -} from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import { usePageBuilderSettings } from "@webiny/app-page-builder/admin/hooks/usePageBuilderSettings"; +import { useSiteStatus } from "@webiny/app-page-builder/admin/hooks/useSiteStatus"; import { ListItemGraphic } from "@webiny/ui/List"; import { Icon } from "@webiny/ui/Icon"; import { ReactComponent as PreviewIcon } from "@webiny/app-page-builder/admin/assets/visibility.svg"; -import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/configureDomain"; +import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/useConfigureDomain"; const openTarget = window.Cypress ? "_self" : "_blank"; From dd21a649b929983984c65c13abe93ecef6279432 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Mon, 29 Jun 2020 19:01:40 +0530 Subject: [PATCH 33/58] refactor(app-page-builder): rename `configureDomain` file add translator and improve the message --- ...configureDomain.tsx => useConfigureDomain.tsx} | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename packages/app-page-builder/src/utils/{configureDomain.tsx => useConfigureDomain.tsx} (68%) diff --git a/packages/app-page-builder/src/utils/configureDomain.tsx b/packages/app-page-builder/src/utils/useConfigureDomain.tsx similarity index 68% rename from packages/app-page-builder/src/utils/configureDomain.tsx rename to packages/app-page-builder/src/utils/useConfigureDomain.tsx index 566be53feca..2caf02fba77 100644 --- a/packages/app-page-builder/src/utils/configureDomain.tsx +++ b/packages/app-page-builder/src/utils/useConfigureDomain.tsx @@ -18,20 +18,21 @@ export const ConfigureDomainMessage = ({ domain }) => { const isLocalHost = domain && domain.includes("localhost"); return ( - No site is running at {domain} + {t`No site is running at`} {domain}. +

    {isLocalHost ? ( - Either start the server by cd apps/site && yarn start{" "} + {t`Either start the server by running`} cd apps/site && yarn start{" "} ) : ( - Either deploy the site by running yarn webiny deploy apps{" "} + {t`Either deploy the site by running`} yarn webiny deploy apps{" "} )}
    - or update the domain by going into{" "} - page builder settings + {t`or update the domain by going into the`}{" "} + {t`page builder settings.`}
    ); }; @@ -44,8 +45,8 @@ export const useConfigureDomainDialog = (domain, onAccept = null) => { showDialog(, { title: configureDomainTitle, actions: { - accept: { label: "Refresh", onClick: onAccept }, - cancel: { label: "Cancel" } + accept: { label: t`Retry`, onClick: onAccept }, + cancel: { label: t`Cancel` } } }); } From 070794668636d83f43cf1e07956d9d3e46239ef5 Mon Sep 17 00:00:00 2001 From: Ashutosh Date: Tue, 30 Jun 2020 08:36:38 +0530 Subject: [PATCH 34/58] refactor(app-page-builder): move `useConfigureDomain` to hooks wrap `handlePreviewClick` in `useCallback` hook --- .../src/admin/hooks/useConfigureDomain/index.ts | 1 + .../hooks/useConfigureDomain}/useConfigureDomain.tsx | 0 .../header/pageOptionsMenu/PageOptionsMenu.tsx | 8 ++++---- .../admin/plugins/pageDetails/pageRevisions/Revision.tsx | 4 ++-- .../plugins/defaultBar/components/PreviewPageButton.tsx | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 packages/app-page-builder/src/admin/hooks/useConfigureDomain/index.ts rename packages/app-page-builder/src/{utils => admin/hooks/useConfigureDomain}/useConfigureDomain.tsx (100%) diff --git a/packages/app-page-builder/src/admin/hooks/useConfigureDomain/index.ts b/packages/app-page-builder/src/admin/hooks/useConfigureDomain/index.ts new file mode 100644 index 00000000000..089959441c3 --- /dev/null +++ b/packages/app-page-builder/src/admin/hooks/useConfigureDomain/index.ts @@ -0,0 +1 @@ +export { useConfigureDomainDialog } from "./useConfigureDomain"; diff --git a/packages/app-page-builder/src/utils/useConfigureDomain.tsx b/packages/app-page-builder/src/admin/hooks/useConfigureDomain/useConfigureDomain.tsx similarity index 100% rename from packages/app-page-builder/src/utils/useConfigureDomain.tsx rename to packages/app-page-builder/src/admin/hooks/useConfigureDomain/useConfigureDomain.tsx diff --git a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx index 1d392ca470f..6e45489904b 100644 --- a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx +++ b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback } from "react"; import { IconButton } from "@webiny/ui/Button"; import { Icon } from "@webiny/ui/Icon"; import { ReactComponent as MoreVerticalIcon } from "@webiny/app-page-builder/admin/assets/more_vert.svg"; @@ -14,9 +14,9 @@ import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; import classNames from "classnames"; import { setHomePage } from "./graphql"; import { useConfirmationDialog } from "@webiny/app-admin/hooks/useConfirmationDialog"; +import { useConfigureDomainDialog } from "@webiny/app-page-builder/admin/hooks/useConfigureDomain"; import { getPlugins } from "@webiny/plugins"; import { PbPageDetailsHeaderRightOptionsMenuItemPlugin } from "@webiny/app-page-builder/types"; -import { useConfigureDomainDialog } from "@webiny/app-page-builder/utils/useConfigureDomain"; const menuStyles = css({ width: 250, @@ -53,7 +53,7 @@ const PageOptionsMenu = props => { // We must prevent opening in new tab - Cypress doesn't work with new tabs. const target = window.Cypress ? "_self" : "_blank"; - const handlePreviewClick = () => { + const handlePreviewClick = useCallback(() => { const url = page.locked ? getPageUrl(page) : getPagePreviewUrl(page); if (isSiteRunning) { @@ -61,7 +61,7 @@ const PageOptionsMenu = props => { } else { showConfigureDomainDialog(); } - }; + }, [page.id, isSiteRunning, getPagePreviewUrl, getPageUrl]); return ( Date: Tue, 30 Jun 2020 15:11:16 +0530 Subject: [PATCH 35/58] refactor(app-page-builder): move `url` outside of `handlePreviewClick` --- .../pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx index 6e45489904b..f85a2e72e65 100644 --- a/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx +++ b/packages/app-page-builder/src/admin/plugins/pageDetails/header/pageOptionsMenu/PageOptionsMenu.tsx @@ -52,16 +52,15 @@ const PageOptionsMenu = props => { // We must prevent opening in new tab - Cypress doesn't work with new tabs. const target = window.Cypress ? "_self" : "_blank"; + const url = page.locked ? getPageUrl(page) : getPagePreviewUrl(page); const handlePreviewClick = useCallback(() => { - const url = page.locked ? getPageUrl(page) : getPagePreviewUrl(page); - if (isSiteRunning) { window.open(url, target, "noopener"); } else { showConfigureDomainDialog(); } - }, [page.id, isSiteRunning, getPagePreviewUrl, getPageUrl]); + }, [url, isSiteRunning]); return ( Date: Tue, 30 Jun 2020 12:44:31 +0200 Subject: [PATCH 36/58] docs: add development overview doc (#1087) --- docs/DEVELOPMENT_OVERVIEW.md | 157 +++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 docs/DEVELOPMENT_OVERVIEW.md diff --git a/docs/DEVELOPMENT_OVERVIEW.md b/docs/DEVELOPMENT_OVERVIEW.md new file mode 100644 index 00000000000..c065241eed5 --- /dev/null +++ b/docs/DEVELOPMENT_OVERVIEW.md @@ -0,0 +1,157 @@ +# DEVELOPMENT OVERVIEW + +In this article, we'll explain how Webiny works so you can confidently start contributing + +### Webiny Local Setup + +First things first, we will fork and clone [webiny-js repo](https://github.com/webiny/webiny-js). + +* Install all dependencies by running ```yarn``` + +* Run `yarn setup-repo`. + + What happens when we run `yarn setup-repo`? + +The will set up the necessary environment config files and build packages to generate dist folders and TS declarations. + +This task is possible by `Lerna`, we will explain below the importance of using `Lerna`. + +* You need to update the DB connection string, edit your `sample-project/.env.json` file. + Configure your MongoDB connection data in `sample-project/.env.json`. + See https://docs.webiny.com/docs/get-started/quick-start/#2-setup-database-connection for more details. + + + +### Lerna + +Lerna optimizes the workflow around managing multi-package repositories with `git` and `yarn` in our case. + + +:::note Note: + `Yarn` manages the workspaces and `Lerna` publishes packages and run commands across workspaces. In `monorepo` lingo, a workspace is a single package. +::: + + * We will use Lerna to watch for changes in packages we are working on. + The `watch` script transpile typescript files into javascript. + + We use `--scope` to watch a particular package. + + `--scope=@webiny/package_name` as specified in the `package.json` itself. + + * Lerna will match the scope parameter, and will say that I found this package that runs into this path. + + * How to run `watch` in many scopes? + + `--scope=@webiny/package_name --scope=@webiny/package_name --parallel` + + :::info + The `parallel` parameter will tell Lerna to run typescript compilations. + This parameter is dangerous when you want to build packages. + ::: + + * We add the `--stream` parameter, to stream the output to our terminal. + + * Then we will run `lerna run build --stream` to build packages. + + * This is the only thing you need to do to have the developer cycle. + +:::info +The same thing goes for API packages. +::: + +### Going Live + +Now that we have our project, it's almost ready to be deployed. To begin the development, we need to deploy an instance of an API for React apps to talk to. + +The apps need to talk to an API, Webiny is serverless and we do not support a local API development. You will work with Lambdas, CDNs. The local environment is the only actual build in system. You need to deploy a local API, deploying an instance of API and we will use it for local development of our React app. + +The `resource` file has the items which we will deploy, you can see the entire stack definition in here. + +The file organization is open to developer decisions. + +We notice that Webiny is set up as a monorepo so we can manage our packages, for both API and React. + +This is the reason we use yarn because its workspace management makes working with monorepos enjoyable. + +Check out our [project structure](https://docs.webiny.com/docs/deep-dive/project-structure) and content here. + +When you navigate to `packages` you will see around 70 packages. We also have the `sample-project` folder. This is the project which the user will get when creating a new webiny project. The `sample-project` is the project we simulate users project, we will use it as a sandbox. + +- Deploy your API by running +`npx webiny deploy api --env=local` from the `sample-project` folder. + +:::note NOTES: +Webiny should run from the root of the Webiny project. Since `sample-project` folder is a sandbox, this is the place to run your webiny commands from. + +Run `npx webiny --help` to see the available commands in our CLI +::: + +- Once deployed, it will update your React apps `.env.json` files with the necessary variables. + +Now we have headless cms configured, which has its entry point, the one thing we care about is the main GraphQL API. It can take from 3 - 15 minutes to be available. + +:::note NOTES: +Within our repository, you should use npx to run Webiny CLI. + +For example, ```npx webiny deploy api --env=local``` + +Why? Because `npx` will resolve the CLI binary to the node_modules in the root of your repository. +::: + +- Begin working on React apps by navigating to `sample-project/apps/{admin|site}` and run `yarn start`. + +- React apps are regular `create-react-app` apps, modified, but all the CRA rules apply. + +- When working on a particular package run `lerna run watch --scope=name_of_package`. It will build your changes into the corresponding `dist` folder. + + React app build will rebuild and hot-reload changes that happen in the dist folder of all related packages. + + - The easiest way to run a watch is by running ```lerna run watch --scope=name_of_package --stream --parallel```. + +Now that we set up webiny project, we can notice that webiny is set up as a monorepo so you can manage your packages, for both API and React. + +This is the reason we use yarn because its workspace management makes working with monorepos enjoyable. + +### What happens when we build packages? + +Let's see an example package, the `api-cookie-policy`. + +![Package content](/img/deep-dive/architecture/webiny-package-content.png) + +The package contains the `dist` and the `src` folders. + +The `dist` folder will be where our actually compiled code will take place, from there we will be able to publish to npm right away. + +The `src` folder will be the place where we will write our code. + +We have two `tsconfig` files: +- `tsconfig.build.json` - contains base configuration for developing using typescript. +- `tsconfig.json` - extends `tsconfig.build.json` and is configured to play with your IDE for instant type checks. + +Since we have `src` and `dist` folders, yarn links packages as workspaces. Each package is a separate workspace. + +One important thing for development is that when you change something in these packages in the `src` + +When adding changes in `src` folder in these packages, we need to run `build` on that particular package. The `build` compiles the code and puts it into the `dist` folder. + + +### Tools + +:::info +we are using a built-in `npm` feature, which is a `postinstall` hook, to run our custom script. +::: + +We use `publishConfig` to set up config values and use them at publish-time. When we run publish, package linking will work based on the `publishConfig` setup. `Lerna` will get the information from the `publishConfig` parameters, where the compiled code lives. + +```ts + "publishConfig": { + "access": "public", + "directory": "dist" +}, +``` \ No newline at end of file From d7c331636adbd05061c1860e7edc0e7593f09256 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Tue, 30 Jun 2020 11:51:01 +0200 Subject: [PATCH 37/58] test: improve test utils --- packages/api-headless-cms/__tests__/utils/useGqlHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api-headless-cms/__tests__/utils/useGqlHandler.js b/packages/api-headless-cms/__tests__/utils/useGqlHandler.js index 663e2af742e..1d98d31f81d 100644 --- a/packages/api-headless-cms/__tests__/utils/useGqlHandler.js +++ b/packages/api-headless-cms/__tests__/utils/useGqlHandler.js @@ -7,7 +7,7 @@ import apolloServerPlugins from "@webiny/handler-apollo-server"; import settingsManagerPlugins from "@webiny/api-settings-manager/client"; import headlessCmsPlugins from "@webiny/api-headless-cms/plugins"; -export default ({ database }) => { +export default ({ database } = {}) => { if (!database) { database = new Database(); } From 4e0c4518eff4a3145872afae01e2cbe9708873fb Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Tue, 30 Jun 2020 13:00:38 +0200 Subject: [PATCH 38/58] fix: export mock IDs for direct usage in tests --- packages/api-headless-cms/src/testing/createEnvironment.ts | 4 +++- .../api-headless-cms/src/testing/createEnvironmentAlias.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/api-headless-cms/src/testing/createEnvironment.ts b/packages/api-headless-cms/src/testing/createEnvironment.ts index c48b08499a6..1d8283055a5 100644 --- a/packages/api-headless-cms/src/testing/createEnvironment.ts +++ b/packages/api-headless-cms/src/testing/createEnvironment.ts @@ -1,6 +1,8 @@ +export const environmentId = "e1e1e1e1e1e1e1e1e1e1e1e1"; + export default ({ database }) => database.collection("CmsEnvironment").insert({ - id: "e1e1e1e1e1e1e1e1e1e1e1e1", + id: environmentId, name: "Initial Environment", description: "This is the initial environment.", createdFrom: null diff --git a/packages/api-headless-cms/src/testing/createEnvironmentAlias.ts b/packages/api-headless-cms/src/testing/createEnvironmentAlias.ts index d3509f8be2f..460f1d9f1b0 100644 --- a/packages/api-headless-cms/src/testing/createEnvironmentAlias.ts +++ b/packages/api-headless-cms/src/testing/createEnvironmentAlias.ts @@ -1,6 +1,8 @@ +export const environmentAliasId = "ea1ea1ea1ea1ea1ea1ea1ea1"; + export default ({ database, environmentId }) => database.collection("CmsEnvironmentAlias").insert({ - id: "ea1ea1ea1ea1ea1ea1ea1ea1", + id: environmentAliasId, name: "Production", slug: "production", description: 'This is the "production" environment alias', From fc02d556fb1a41df790ec750e9f3354e3aa5b61d Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Tue, 30 Jun 2020 13:01:20 +0200 Subject: [PATCH 39/58] test: rely on isEqual and mocks, instead of checking properties separately --- .../__tests__/environments.test.js | 71 ++++++++++--------- .../__tests__/mocks/environments.js | 53 ++++++++++++++ 2 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 packages/api-headless-cms/__tests__/mocks/environments.js diff --git a/packages/api-headless-cms/__tests__/environments.test.js b/packages/api-headless-cms/__tests__/environments.test.js index ec59ff3fdbf..915689d1a89 100644 --- a/packages/api-headless-cms/__tests__/environments.test.js +++ b/packages/api-headless-cms/__tests__/environments.test.js @@ -1,6 +1,6 @@ import useGqlHandler from "./utils/useGqlHandler"; -import { Database } from "@commodo/fields-storage-nedb"; import { createEnvironment, createEnvironmentAlias } from "@webiny/api-headless-cms/testing"; +import mocks from "./mocks/environments"; const CREATE_ENVIRONMENT = /* GraphQL */ ` mutation createEnvironment($data: CmsEnvironmentInput!) { @@ -67,16 +67,16 @@ const LIST_ENVIRONMENTS = /* GraphQL */ ` `; describe("Environments test", () => { - const database = new Database(); - const { invoke } = useGqlHandler({ database }); + const { invoke, database } = useGqlHandler(); - const initial = {}; - - beforeAll(async () => { - initial.environment = await createEnvironment({ database }); + afterEach(async () => { + await database.collection("CmsEnvironment").remove({}, { multi: true }); + await database.collection("CmsEnvironmentAlias").remove({}, { multi: true }); }); it("should create a new environment", async () => { + const initial = { environment: await createEnvironment({ database }) }; + let [body] = await invoke({ body: { query: CREATE_ENVIRONMENT, @@ -111,10 +111,23 @@ describe("Environments test", () => { }); expect(body.data.cms.createEnvironment.data.id).toBeTruthy(); - expect(body.data.cms.createEnvironment.data.createdFrom.id).toBeTruthy(); + expect(body.data.cms.createEnvironment.data.createdFrom.id).toBe(initial.environment.id); }); it(`should utilize "where" filtering correctly`, async () => { + const initial = { environment: await createEnvironment({ database }) }; + await invoke({ + body: { + query: CREATE_ENVIRONMENT, + variables: { + data: { + name: "new-environment-1", + createdFrom: initial.environment.id + } + } + } + }); + let [body] = await invoke({ body: { query: GET_ENVIRONMENT, @@ -126,57 +139,47 @@ describe("Environments test", () => { } }); - expect(body.data.cms.getEnvironment.data.id).toBeTruthy(); - // check for aliases - expect(body.data.cms.getEnvironment.data.environmentAliases).toBeTruthy(); - expect(body.data.cms.getEnvironment.data.environmentAliases.length).toBe(1); - expect(body.data.cms.getEnvironment.data.environmentAliases[0].name).toBeTruthy(); + expect(body).toEqual( + mocks.getEnvironments({ environmentId: body.data.cms.getEnvironment.data.id }) + ); }); it("should be able to list environments with alias", async () => { - let [body] = await invoke({ - body: { - query: LIST_ENVIRONMENTS - } - }); - - const initialListLength = body.data.cms.listEnvironments.data.length; + const initial = { environment: await createEnvironment({ database }) }; + let environmentsList = await database.collection("CmsEnvironment").find(); + expect(environmentsList.length).toBe(1); const [createdEnvironmentBody] = await invoke({ body: { query: CREATE_ENVIRONMENT, variables: { data: { - name: "new-environment-1", + name: "new-environment-list-envs-test-1", createdFrom: initial.environment.id } } } }); + environmentsList = await database.collection("CmsEnvironment").find(); + expect(environmentsList.length).toBe(2); + // link `environment with alias` await createEnvironmentAlias({ database, environmentId: createdEnvironmentBody.data.cms.createEnvironment.data.id }); - [body] = await invoke({ + let [body] = await invoke({ body: { query: LIST_ENVIRONMENTS } }); - expect(body.data.cms.listEnvironments.data.length).toBe(initialListLength + 1); - - expect( - body.data.cms.listEnvironments.data[initialListLength].environmentAliases.length - ).toBe(1); - - expect( - body.data.cms.listEnvironments.data[initialListLength].environmentAliases[0].name - ).toBeTruthy(); - expect( - body.data.cms.listEnvironments.data[initialListLength].environmentAliases[0].slug - ).toBeTruthy(); + expect(body).toEqual( + mocks.listEnvironments({ + environmentId: createdEnvironmentBody.data.cms.createEnvironment.data.id + }) + ); }); }); diff --git a/packages/api-headless-cms/__tests__/mocks/environments.js b/packages/api-headless-cms/__tests__/mocks/environments.js new file mode 100644 index 00000000000..cac3aeee90b --- /dev/null +++ b/packages/api-headless-cms/__tests__/mocks/environments.js @@ -0,0 +1,53 @@ +import { environmentId as mockedEnvironmentId } from "@webiny/api-headless-cms/testing/createEnvironment"; +import { environmentAliasId } from "@webiny/api-headless-cms/testing/createEnvironmentAlias"; + +export default { + getEnvironments: ({ environmentId }) => ({ + data: { + cms: { + getEnvironment: { + data: { + createdFrom: { + id: "e1e1e1e1e1e1e1e1e1e1e1e1", + name: "Initial Environment" + }, + environmentAliases: [], + id: environmentId, + name: "new-environment-1" + } + } + } + } + }), + listEnvironments: ({ environmentId }) => ({ + data: { + cms: { + listEnvironments: { + data: [ + { + id: mockedEnvironmentId, + name: "Initial Environment", + createdFrom: null, + environmentAliases: [] + }, + { + id: environmentId, + name: "new-environment-list-envs-test-1", + createdFrom: { + id: mockedEnvironmentId, + name: "Initial Environment" + }, + environmentAliases: [ + { + id: environmentAliasId, + name: "Production", + slug: "production" + } + ] + } + ] + } + } + } + }) +}; From 6512d07d9f9b9b867be0cbaf26429fd1a0942744 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Tue, 30 Jun 2020 13:17:38 -0400 Subject: [PATCH 40/58] fix(headless): fix load of environment api for aliases --- .../src/components/EnvironmentInfoDialog.tsx | 103 ++++++++---------- .../src/plugins/Menu/Navigation/index.tsx | 20 ++-- .../components/EnvironmentInfoDialog.tsx | 9 +- .../app-headless-cms/src/admin/icons/info.svg | 14 +-- .../EnvironmentAliasesDataList.tsx | 35 +++--- 5 files changed, 87 insertions(+), 94 deletions(-) diff --git a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx index 287cde985f9..7a63ce0b127 100644 --- a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx +++ b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx @@ -6,16 +6,12 @@ import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; import { CopyButton } from "@webiny/ui/Button"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; import { LIST_ENVIRONMENT_ALIASES } from "./views/EnvironmentAliases/graphql"; +import { toLower } from "lodash"; export type NewContentModelDialogProps = { open: boolean; onClose: () => void; name: string; - url: { - manage: string; - preview: string; - read: string; - }; }; const t = i18n.ns("app-admin/navigation"); @@ -52,16 +48,15 @@ const style = { const EnvironmentInfoDialog: React.FC = ({ open, onClose, - name, - url + name }) => { const { showSnackbar } = useSnackbar(); const graphqlApiUrl = process.env.REACT_APP_API_URL; - const [aliases, setAliases] = useState([]); + const [totalAliases, setTotalAliases] = useState([]); useQuery(LIST_ENVIRONMENT_ALIASES, { onCompleted: data => { - setAliases(data.cms.environmentAliases.data.filter((elem) => elem.environment.name === name)); + setTotalAliases(data.cms.environmentAliases.data); } }); @@ -75,55 +70,49 @@ const EnvironmentInfoDialog: React.FC = ({ {t`Environment: `}{name}
    - { - url && aliases.length > 0 ? -
    -
    -

    GraphQL URL:

    -

    {process.env.REACT_APP_GRAPHQL_API_URL}

    - showSnackbar("Successfully copied!")} - /> -
    - { - aliases.map((elem) => { - return( -
    -

    Alias: {elem.name}

    -
    -

    Content Delivery API:

    -

    {`${graphqlApiUrl}${elem.url.read}`}

    - showSnackbar("Successfully copied!")} - /> -
    -
    -

    Content Preview API:

    -

    {`${graphqlApiUrl}${elem.url.preview}`}

    - showSnackbar("Successfully copied!")} - /> -
    -
    -

    Content Management API:

    -

    {`${graphqlApiUrl}${elem.url.manage}`}

    - showSnackbar("Successfully copied!")} - /> -
    -
    - ) - }) - } -
    - :
    - {t`Loading your URL's shortly...`} +
    +
    +

    GraphQL URL:

    +

    {process.env.REACT_APP_GRAPHQL_API_URL}

    + showSnackbar("Successfully copied!")} + />
    - } + { + totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { + return( +
    +

    Alias: {elem.name}

    +
    +

    Content Delivery API:

    +

    {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    +

    Content Preview API:

    +

    {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    +

    Content Management API:

    +

    {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`}

    + showSnackbar("Successfully copied!")} + /> +
    +
    + ) + }) + } +
    diff --git a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx index 257f33d8de2..124d6ec7b73 100755 --- a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx +++ b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx @@ -16,6 +16,7 @@ import { ReactComponent as GithubIcon } from "@webiny/app-admin/assets/icons/git import { ReactComponent as InfoIcon } from "@webiny/app-admin/assets/icons/info.svg"; import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; + import { i18n } from "@webiny/app/i18n"; const t = i18n.ns("app-admin/navigation"); @@ -83,20 +84,13 @@ const Navigation = () => { e.preventDefault(); e.stopPropagation(); setInfoOpened(true); - }}> + }} + > }/> {t`API information`} - { - setInfoOpened(false); - }} - name={currentEnvironment ? currentEnvironment.name : t`N/A`} - url={currentEnvironment ? currentEnvironment.environmentAlias.url : undefined} - />
    @@ -148,6 +142,14 @@ const Navigation = () => { + { + currentEnvironment && + setInfoOpened(false)} + name={currentEnvironment.name || t`N\A`} + /> + } ); diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx index 5c5eb40c483..667ebb47820 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -57,11 +57,10 @@ const EnvironmentInfoDialog: React.FC = ({ }) => { const { showSnackbar } = useSnackbar(); const graphqlApiUrl = process.env.REACT_APP_API_URL; - const [aliases, setAliases] = useState([]); - + const [totalAliases, setTotalAliases] = useState([]); useQuery(LIST_ENVIRONMENT_ALIASES, { onCompleted: data => { - setAliases(data.cms.environmentAliases.data.filter((elem) => elem.environment.name === name)); + setTotalAliases(data.cms.environmentAliases.data); } }); @@ -76,7 +75,7 @@ const EnvironmentInfoDialog: React.FC = ({
    { - url && aliases.length > 0 ? + url ?

    GraphQL URL:

    @@ -87,7 +86,7 @@ const EnvironmentInfoDialog: React.FC = ({ />
    { - aliases.map((elem) => { + totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { return(

    Alias: {elem.name}

    diff --git a/packages/app-headless-cms/src/admin/icons/info.svg b/packages/app-headless-cms/src/admin/icons/info.svg index 98202899d2f..d50ac3989a0 100644 --- a/packages/app-headless-cms/src/admin/icons/info.svg +++ b/packages/app-headless-cms/src/admin/icons/info.svg @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index 57d69b8ade3..1656cd756a0 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -41,7 +41,7 @@ const EnvironmentAliasesDataList = () => { const { actions, list } = useCrud(); const [infoOpened, setInfoOpened] = useState(false); const [selectedInfo, setSelectedInfo] = useState({ - name: t`N\A`, + name: "", url: { manage: "", preview: "", @@ -80,21 +80,24 @@ const EnvironmentAliasesDataList = () => {
    {item.name}{" "} -
    { - e.preventDefault(); - e.stopPropagation(); - setInfoOpened(true); - setSelectedInfo(item); - }}> - -
    -
    - setInfoOpened(false)} - name={selectedInfo.name} - url={selectedInfo.url} - /> +
    { + e.preventDefault(); + e.stopPropagation(); + setInfoOpened(true); + setSelectedInfo(item); + }}> + +
    + + { + selectedInfo.name && + setInfoOpened(false)} + name={selectedInfo.name} + url={selectedInfo.url} + /> + }
    {item.default && ( {t`(default)`} From 6cf1a342dd9ce3b77c89a8d1b4f998144e5d8d94 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 2 Jul 2020 11:16:51 -0400 Subject: [PATCH 41/58] wip(headless): change location of dialog so background is grey --- .../EnvironmentAliasesDataList.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index 1656cd756a0..fcaa0101723 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -74,6 +74,15 @@ const EnvironmentAliasesDataList = () => { > {({ data, isSelected, select }) => ( + { + selectedInfo.name && + setInfoOpened(false)} + name={selectedInfo.name} + url={selectedInfo.url} + /> + } {data.map(item => ( select(item)}> @@ -89,15 +98,6 @@ const EnvironmentAliasesDataList = () => {
    - { - selectedInfo.name && - setInfoOpened(false)} - name={selectedInfo.name} - url={selectedInfo.url} - /> - }
    {item.default && ( {t`(default)`} From 3a14ecf719cfe43cd64579d9569a71ea0b5d9982 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 2 Jul 2020 18:27:43 -0400 Subject: [PATCH 42/58] refactor(headless): add styling to info dialog and links to api urls --- .../src/components/EnvironmentInfoDialog.tsx | 54 ++++++++++++---- .../components/EnvironmentInfoDialog.tsx | 63 ++++++++++++++----- .../EnvironmentAliasesDataList.tsx | 9 ++- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx index 7a63ce0b127..aa2fab6a135 100644 --- a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx +++ b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx @@ -7,6 +7,7 @@ import { CopyButton } from "@webiny/ui/Button"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; import { LIST_ENVIRONMENT_ALIASES } from "./views/EnvironmentAliases/graphql"; import { toLower } from "lodash"; +import { Typography } from "@webiny/ui/Typography"; export type NewContentModelDialogProps = { open: boolean; @@ -32,11 +33,9 @@ const style = { fontWeight: "bold" }), aliasTitle: css({ - textDecoration: "underline", minWidth: "200px" }), api: css({ - fontSize: "1.25rem", fontWeight: "bold", minWidth: "200px" }), @@ -72,8 +71,14 @@ const EnvironmentInfoDialog: React.FC = ({
    -

    GraphQL URL:

    -

    {process.env.REACT_APP_GRAPHQL_API_URL}

    + GraphQL API: +
    + {process.env.REACT_APP_GRAPHQL_API_URL} + showSnackbar("Successfully copied!")} @@ -83,31 +88,58 @@ const EnvironmentInfoDialog: React.FC = ({ totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { return(
    -

    Alias: {elem.name}

    + + Alias: {elem.name} +
    -

    Content Delivery API:

    -

    {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`}

    + + Content Delivery API: + + + {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`} + showSnackbar("Successfully copied!")} />
    -

    Content Preview API:

    -

    {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`}

    + + Content Preview API: + + + {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`} + showSnackbar("Successfully copied!")} />
    -

    Content Management API:

    -

    {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`}

    + + Content Management API: + + + {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`} + showSnackbar("Successfully copied!")} />
    +

    ) }) diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx index 667ebb47820..ee94f5b2b86 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -6,6 +6,8 @@ import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; import { CopyButton } from "@webiny/ui/Button"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; +import { Typography } from "@webiny/ui/Typography"; +import {toLower} from "lodash"; export type NewContentModelDialogProps = { open: boolean; @@ -36,11 +38,9 @@ const style = { fontWeight: "bold" }), aliasTitle: css({ - textDecoration: "underline", minWidth: "200px" }), api: css({ - fontSize: "1.25rem", fontWeight: "bold", minWidth: "200px" }), @@ -78,8 +78,14 @@ const EnvironmentInfoDialog: React.FC = ({ url ?
    -

    GraphQL URL:

    -

    {process.env.REACT_APP_GRAPHQL_API_URL}

    + GraphQL API: + + {process.env.REACT_APP_GRAPHQL_API_URL} + showSnackbar("Successfully copied!")} @@ -89,31 +95,58 @@ const EnvironmentInfoDialog: React.FC = ({ totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { return(
    -

    Alias: {elem.name}

    -
    -

    Content Delivery API:

    -

    {`${graphqlApiUrl}${elem.url.read}`}

    + + Alias: {elem.name} + +
    + + Content Delivery API: + + + {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`} + showSnackbar("Successfully copied!")} />
    -

    Content Preview API:

    -

    {`${graphqlApiUrl}${elem.url.preview}`}

    + + Content Preview API: + + + {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`} + showSnackbar("Successfully copied!")} />
    -

    Content Management API:

    -

    {`${graphqlApiUrl}${elem.url.manage}`}

    + + Content Management API: + + + {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`} + showSnackbar("Successfully copied!")} />
    +

    ) }) diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index fcaa0101723..1a42d72212a 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -26,6 +26,7 @@ const style = { color: "var(--mdc-theme-primary)" }), icon: css({ + color: "rgba(255, 255, 255, 0.54)", width: 16, height: 16, marginTop: "4px", @@ -41,7 +42,9 @@ const EnvironmentAliasesDataList = () => { const { actions, list } = useCrud(); const [infoOpened, setInfoOpened] = useState(false); const [selectedInfo, setSelectedInfo] = useState({ - name: "", + environment: { + name: "" + }, url: { manage: "", preview: "", @@ -75,11 +78,11 @@ const EnvironmentAliasesDataList = () => { {({ data, isSelected, select }) => ( { - selectedInfo.name && + selectedInfo.environment.name && setInfoOpened(false)} - name={selectedInfo.name} + name={selectedInfo.environment.name} url={selectedInfo.url} /> } From 8689fc261c0c5b7c919dbc31a65d765dd2af5f8e Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Mon, 6 Jul 2020 14:51:06 -0400 Subject: [PATCH 43/58] fix(headless): add tooltip and label for headless api links --- .../src/components/EnvironmentInfoDialog.tsx | 14 ++++++++------ .../admin/components/EnvironmentInfoDialog.tsx | 16 +++++++++------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx index aa2fab6a135..7785eb05e6b 100644 --- a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx +++ b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx @@ -8,6 +8,7 @@ import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; import { LIST_ENVIRONMENT_ALIASES } from "./views/EnvironmentAliases/graphql"; import { toLower } from "lodash"; import { Typography } from "@webiny/ui/Typography"; +import { Tooltip } from "@webiny/ui/Tooltip"; export type NewContentModelDialogProps = { open: boolean; @@ -28,10 +29,6 @@ const style = { display: "flex", alignItems: "center" }), - alias: css({ - fontSize: "1.25rem", - fontWeight: "bold" - }), aliasTitle: css({ minWidth: "200px" }), @@ -71,7 +68,9 @@ const EnvironmentInfoDialog: React.FC = ({
    - GraphQL API: + This link allows you to access content created by different application across Webiny like Page Builder or Form Builder.}> + GraphQL API: + = ({ onCopy={() => showSnackbar("Successfully copied!")} />
    + + Headless CMS - {name} + { totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { return(
    - + Alias: {elem.name}
    diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx index ee94f5b2b86..b96fc0d367c 100644 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx @@ -7,7 +7,8 @@ import { CopyButton } from "@webiny/ui/Button"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; import { Typography } from "@webiny/ui/Typography"; -import {toLower} from "lodash"; +import { toLower } from "lodash"; +import { Tooltip } from "@webiny/ui/Tooltip"; export type NewContentModelDialogProps = { open: boolean; @@ -33,10 +34,6 @@ const style = { display: "flex", alignItems: "center" }), - alias: css({ - fontSize: "1.25rem", - fontWeight: "bold" - }), aliasTitle: css({ minWidth: "200px" }), @@ -78,7 +75,9 @@ const EnvironmentInfoDialog: React.FC = ({ url ?
    + + Headless CMS - {name} + { totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { return(
    - + Alias: {elem.name}
    From 83845fd9b59d615bdd501f3a1f942ba79ad2425c Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Wed, 8 Jul 2020 21:58:28 -0400 Subject: [PATCH 44/58] wip(headless): refactor plugin for alias and non alias use --- .../src/components/EnvironmentInfoDialog.tsx | 134 ++------------ .../views/EnvironmentAliases/graphql.ts | 136 -------------- .../src/plugins/Menu/Navigation/index.tsx | 2 +- packages/app-admin/src/types.ts | 5 + .../components/EnvironmentInfoDialog.tsx | 167 ------------------ .../src/admin/components/ReactGraphqlUrl.tsx | 43 +++++ .../admin/plugins/apiInformationDialog.tsx | 120 +++++++++++++ .../src/admin/plugins/index.ts | 4 +- .../EnvironmentAliasesDataList.tsx | 18 +- 9 files changed, 195 insertions(+), 434 deletions(-) delete mode 100644 packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts delete mode 100644 packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx create mode 100644 packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx create mode 100644 packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx diff --git a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx index 7785eb05e6b..f7bdf3970d1 100644 --- a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx +++ b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx @@ -1,19 +1,15 @@ -import React, { useState } from "react"; -import { useQuery } from '@apollo/react-hooks'; +import React from "react"; import { css } from "emotion"; import { i18n } from "@webiny/app/i18n"; import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; -import { CopyButton } from "@webiny/ui/Button"; -import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; -import { LIST_ENVIRONMENT_ALIASES } from "./views/EnvironmentAliases/graphql"; -import { toLower } from "lodash"; -import { Typography } from "@webiny/ui/Typography"; -import { Tooltip } from "@webiny/ui/Tooltip"; +import { getPlugins } from "@webiny/plugins"; +import { ApiInformationDialog } from "@webiny/app-admin/types"; export type NewContentModelDialogProps = { open: boolean; onClose: () => void; name: string; + aliases: boolean; }; const t = i18n.ns("app-admin/navigation"); @@ -24,38 +20,18 @@ const style = { width: 800, minWidth: 800 } - }), - apiUrl: css({ - display: "flex", - alignItems: "center" - }), - aliasTitle: css({ - minWidth: "200px" - }), - api: css({ - fontWeight: "bold", - minWidth: "200px" - }), - aliasContainer: css({ - marginTop: "10px" }) }; const EnvironmentInfoDialog: React.FC = ({ open, onClose, - name + name, + aliases }) => { - const { showSnackbar } = useSnackbar(); - const graphqlApiUrl = process.env.REACT_APP_API_URL; - const [totalAliases, setTotalAliases] = useState([]); - - useQuery(LIST_ENVIRONMENT_ALIASES, { - onCompleted: data => { - setTotalAliases(data.cms.environmentAliases.data); - } - }); - + const adminInfoPlugins = getPlugins( + "admin-api-information-dialog" + ); return ( = ({ > {t`Environment: `}{name} -
    -
    - - - Headless CMS - {name} - - { - totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { - return( -
    - - Alias: {elem.name} - -
    - - Content Delivery API: - - - {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -
    - - Content Preview API: - - - {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -
    - - Content Management API: - - - {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -

    -
    - ) - }) - } -
    -
    + { + adminInfoPlugins.map(pl => { + return ( +
    + {pl.render({ name, aliases })} +
    + ) + }) + } ) diff --git a/packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts b/packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts deleted file mode 100644 index 075ac715076..00000000000 --- a/packages/app-admin/src/components/views/EnvironmentAliases/graphql.ts +++ /dev/null @@ -1,136 +0,0 @@ -import gql from "graphql-tag"; - -const fields = ` - id - name - slug - description - environment { - id - name - } -`; - -export const LIST_ENVIRONMENT_ALIASES = gql` - query listEnvironmentAliases( - $where: JSON - $sort: JSON - $search: CmsSearchInput - $limit: Int - $after: String - $before: String - ) { - cms { - environmentAliases: listEnvironmentAliases( - where: $where - sort: $sort - search: $search - limit: $limit - after: $after - before: $before - ) { - data { - id - name - slug - createdOn - url { - manage - read - preview - } - environment { - id - name - } - } - meta { - cursors { - next - previous - } - hasNextPage - hasPreviousPage - totalCount - } - } - } - } -`; - -export const GET_ENVIRONMENT_ALIAS_BY_SLUG = gql` - query getEnvironmentAliasBySlug($slug: String) { - cms { - getEnvironmentAlias(where: { slug: $slug }) { - data { - name - id - } - } - } - } -`; - -export const READ_ENVIRONMENT_ALIAS = gql` - query getEnvironmentAlias($id: ID!) { - cms { - environmentAlias: getEnvironmentAlias(id: $id){ - data { - ${fields} - } - error { - code - message - } - } - } - } -`; - -export const CREATE_ENVIRONMENT_ALIAS = gql` - mutation createEnvironmentAlias($data: CmsEnvironmentAliasInput!){ - cms { - environmentAlias: createEnvironmentAlias(data: $data) { - data { - ${fields} - } - error { - code - message - data - } - } - } - } -`; - -export const UPDATE_ENVIRONMENT_ALIAS = gql` - mutation updateEnvironmentAlias($id: ID!, $data: CmsEnvironmentAliasInput!){ - cms { - environmentAlias: updateEnvironmentAlias(id: $id, data: $data) { - data { - ${fields} - } - error { - code - message - data - } - } - } - } -`; - -export const DELETE_ENVIRONMENT_ALIAS = gql` - mutation deleteEnvironmentAlias($id: ID!) { - cms { - deleteEnvironmentAlias(id: $id) { - data - error { - code - message - } - } - } - } -`; diff --git a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx index 124d6ec7b73..15f1d7280b8 100755 --- a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx +++ b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx @@ -16,7 +16,6 @@ import { ReactComponent as GithubIcon } from "@webiny/app-admin/assets/icons/git import { ReactComponent as InfoIcon } from "@webiny/app-admin/assets/icons/info.svg"; import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; - import { i18n } from "@webiny/app/i18n"; const t = i18n.ns("app-admin/navigation"); @@ -148,6 +147,7 @@ const Navigation = () => { open={infoOpened} onClose={() => setInfoOpened(false)} name={currentEnvironment.name || t`N\A`} + aliases={false} /> } diff --git a/packages/app-admin/src/types.ts b/packages/app-admin/src/types.ts index 58d77ab5e28..d43149eee54 100644 --- a/packages/app-admin/src/types.ts +++ b/packages/app-admin/src/types.ts @@ -112,3 +112,8 @@ export type AdminInstallationPlugin = Plugin & { secure: boolean; render({ onInstalled }): React.ReactNode; }; + +export type ApiInformationDialog = Plugin & { + type: "admin-api-information-dialog"; + render(props: { name: string; aliases: boolean }): React.ReactNode; +}; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx b/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx deleted file mode 100644 index b96fc0d367c..00000000000 --- a/packages/app-headless-cms/src/admin/components/EnvironmentInfoDialog.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useState } from "react"; -import { useQuery } from '@apollo/react-hooks'; -import { css } from "emotion"; -import { i18n } from "@webiny/app/i18n"; -import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; -import { CopyButton } from "@webiny/ui/Button"; -import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; -import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; -import { Typography } from "@webiny/ui/Typography"; -import { toLower } from "lodash"; -import { Tooltip } from "@webiny/ui/Tooltip"; - -export type NewContentModelDialogProps = { - open: boolean; - onClose: () => void; - name: string; - url: { - manage: string; - preview: string; - read: string; - }; -}; - -const t = i18n.ns("app-headless-cms/admin/menus"); - -const style = { - narrowDialog: css({ - ".mdc-dialog__surface": { - width: 800, - minWidth: 800 - } - }), - apiUrl: css({ - display: "flex", - alignItems: "center" - }), - aliasTitle: css({ - minWidth: "200px" - }), - api: css({ - fontWeight: "bold", - minWidth: "200px" - }), - aliasContainer: css({ - marginTop: "10px" - }) -}; - -const EnvironmentInfoDialog: React.FC = ({ - open, - onClose, - name, - url -}) => { - const { showSnackbar } = useSnackbar(); - const graphqlApiUrl = process.env.REACT_APP_API_URL; - const [totalAliases, setTotalAliases] = useState([]); - useQuery(LIST_ENVIRONMENT_ALIASES, { - onCompleted: data => { - setTotalAliases(data.cms.environmentAliases.data); - } - }); - - return ( - - {t`Environment: `}{name} - -
    - { - url ? -
    -
    - This link allows you to access content created by different application across Webiny like Page Builder or Form Builder.}> - GraphQL API: - - - {process.env.REACT_APP_GRAPHQL_API_URL} - - showSnackbar("Successfully copied!")} - /> -
    - - Headless CMS - {name} - - { - totalAliases.filter((elem) => elem.environment.name === name).map((elem) => { - return( -
    - - Alias: {elem.name} - -
    - - Content Delivery API: - - - {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -
    - - Content Preview API: - - - {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -
    - - Content Management API: - - - {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -

    -
    - ) - }) - } -
    - :
    - {t`Loading your URL's shortly...`} -
    - } -
    -
    -
    - ) -}; - -export default EnvironmentInfoDialog; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx b/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx new file mode 100644 index 00000000000..31518b58455 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { css } from "emotion"; +import { CopyButton } from "@webiny/ui/Button"; +import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; +import { Typography } from "@webiny/ui/Typography"; +import { Tooltip } from "@webiny/ui/Tooltip"; + +const style = { + apiUrl: css({ + display: "flex", + alignItems: "center" + }), + api: css({ + fontWeight: "bold", + minWidth: "200px" + }) +}; + +const ReactGraphqlUrl = () => { + const { showSnackbar } = useSnackbar(); + + return ( +
    + This link allows you to access content created by different application across Webiny like Page Builder or Form Builder.}> + GraphQL API: + + + {process.env.REACT_APP_GRAPHQL_API_URL} + + showSnackbar("Successfully copied!")} + /> +

    +
    + ) +}; + +export default ReactGraphqlUrl; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx new file mode 100644 index 00000000000..67dc3741bef --- /dev/null +++ b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx @@ -0,0 +1,120 @@ +import React, { useState } from "react"; +import { useQuery } from '@apollo/react-hooks'; +import { css } from "emotion"; +import { ApiInformationDialog } from "@webiny/app-admin/types"; +import { CopyButton } from "@webiny/ui/Button"; +import { useCms } from "@webiny/app-headless-cms/admin/hooks"; +import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; +import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; +import { Typography } from "@webiny/ui/Typography"; +import { toLower } from "lodash"; +import ReactGraphqlUrl from "@webiny/app-headless-cms/admin/components/ReactGraphqlUrl"; + +const style = { + apiUrl: css({ + display: "flex", + alignItems: "center" + }), + aliasTitle: css({ + minWidth: "200px" + }), + aliasContainer: css({ + marginTop: "10px" + }) +}; + +const plugin: ApiInformationDialog = { + type: "admin-api-information-dialog", + name: "admin-api-information-dialog-headless-cms", + render({ name, aliases }) { + const { showSnackbar } = useSnackbar(); + const graphqlApiUrl = process.env.REACT_APP_API_URL; + const [totalAliases, setTotalAliases] = useState([]); + const { + environments: { currentEnvironment } + } = useCms(); + + useQuery(LIST_ENVIRONMENT_ALIASES, { + onCompleted: data => { + setTotalAliases(data.cms.environmentAliases.data); + } + }); + + return ( +
    + {!aliases && } + + Headless CMS - {name} + + { + totalAliases.filter((elem) => { + if (aliases) { + return elem.name === name; + } else { + return elem.environment.name === currentEnvironment.name; + } + }).map((elem) => { + return( +
    + + Alias: {elem.name} + +
    + + Content Delivery API: + + + {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`} + + showSnackbar("Successfully copied!")} + /> +
    +
    + + Content Preview API: + + + {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`} + + showSnackbar("Successfully copied!")} + /> +
    +
    + + Content Management API: + + + {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`} + + showSnackbar("Successfully copied!")} + /> +
    +

    +
    + ) + }) + } +
    + ) + } +}; + +export default plugin; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/plugins/index.ts b/packages/app-headless-cms/src/admin/plugins/index.ts index 547c3f0635e..237dd37b4aa 100644 --- a/packages/app-headless-cms/src/admin/plugins/index.ts +++ b/packages/app-headless-cms/src/admin/plugins/index.ts @@ -12,6 +12,7 @@ import contentRevisions from "./contentDetails/contentRevisions"; import contentModelEditorPlugins from "./../editor/plugins"; import appTemplateRenderer from "./appTemplatePlugins"; import welcomeScreenWidget from "./welcomeScreenWidget"; +import ApiInformationDialog from "./apiInformationDialog"; export default () => [ install, @@ -31,5 +32,6 @@ export default () => [ contentModelEditorPlugins, appTemplateRenderer, - welcomeScreenWidget + welcomeScreenWidget, + ApiInformationDialog ]; diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index 1a42d72212a..f2d29d288d8 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -6,7 +6,7 @@ import { ReactComponent as InformationIcon } from "../../icons/info.svg"; import { css } from "emotion"; import { useCrud } from "@webiny/app-admin/hooks/useCrud"; import { Typography } from "@webiny/ui/Typography"; - +import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; import { DataList, List, @@ -17,7 +17,6 @@ import { ListActions } from "@webiny/ui/List"; import { Link } from "@webiny/react-router"; -import EnvironmentInfoDialog from "../../components/EnvironmentInfoDialog"; const t = i18n.ns("app-headless-cms/admin/environmentAliases/data-list"); @@ -42,14 +41,7 @@ const EnvironmentAliasesDataList = () => { const { actions, list } = useCrud(); const [infoOpened, setInfoOpened] = useState(false); const [selectedInfo, setSelectedInfo] = useState({ - environment: { - name: "" - }, - url: { - manage: "", - preview: "", - read: "" - }, + name: "" }); return ( @@ -78,12 +70,12 @@ const EnvironmentAliasesDataList = () => { {({ data, isSelected, select }) => ( { - selectedInfo.environment.name && + selectedInfo.name && setInfoOpened(false)} - name={selectedInfo.environment.name} - url={selectedInfo.url} + name={selectedInfo.name} + aliases={true} /> } {data.map(item => ( From 023bb17e1d0b0ba7a07868aa73cae89995328041 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 9 Jul 2020 08:46:20 -0400 Subject: [PATCH 45/58] wip(headless): add type to dialog to handle use of graphql api --- .../src/components/EnvironmentInfoDialog.tsx | 6 +-- .../src/plugins/Menu/Navigation/index.tsx | 2 +- packages/app-admin/src/types.ts | 2 +- .../admin/plugins/apiInformationDialog.tsx | 8 +-- .../EnvironmentAliasesDataList.tsx | 2 +- .../Environments/EnvironmentsDataList.tsx | 51 +++++++++++++++++-- 6 files changed, 59 insertions(+), 12 deletions(-) diff --git a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx index f7bdf3970d1..bad4cd2a882 100644 --- a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx +++ b/packages/app-admin/src/components/EnvironmentInfoDialog.tsx @@ -9,7 +9,7 @@ export type NewContentModelDialogProps = { open: boolean; onClose: () => void; name: string; - aliases: boolean; + type: string; }; const t = i18n.ns("app-admin/navigation"); @@ -27,7 +27,7 @@ const EnvironmentInfoDialog: React.FC = ({ open, onClose, name, - aliases + type }) => { const adminInfoPlugins = getPlugins( "admin-api-information-dialog" @@ -45,7 +45,7 @@ const EnvironmentInfoDialog: React.FC = ({ adminInfoPlugins.map(pl => { return (
    - {pl.render({ name, aliases })} + {pl.render({ name, type })}
    ) }) diff --git a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx index 15f1d7280b8..b2bd7984d3f 100755 --- a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx +++ b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx @@ -147,7 +147,7 @@ const Navigation = () => { open={infoOpened} onClose={() => setInfoOpened(false)} name={currentEnvironment.name || t`N\A`} - aliases={false} + type="api" /> } diff --git a/packages/app-admin/src/types.ts b/packages/app-admin/src/types.ts index d43149eee54..893f6289a0c 100644 --- a/packages/app-admin/src/types.ts +++ b/packages/app-admin/src/types.ts @@ -115,5 +115,5 @@ export type AdminInstallationPlugin = Plugin & { export type ApiInformationDialog = Plugin & { type: "admin-api-information-dialog"; - render(props: { name: string; aliases: boolean }): React.ReactNode; + render(props: { name: string; type: string }): React.ReactNode; }; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx index 67dc3741bef..6c2370f2221 100644 --- a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx +++ b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx @@ -26,7 +26,7 @@ const style = { const plugin: ApiInformationDialog = { type: "admin-api-information-dialog", name: "admin-api-information-dialog-headless-cms", - render({ name, aliases }) { + render({ name, type }) { const { showSnackbar } = useSnackbar(); const graphqlApiUrl = process.env.REACT_APP_API_URL; const [totalAliases, setTotalAliases] = useState([]); @@ -42,14 +42,16 @@ const plugin: ApiInformationDialog = { return (
    - {!aliases && } + {type === "api" && } Headless CMS - {name} { totalAliases.filter((elem) => { - if (aliases) { + if (type === "aliases") { return elem.name === name; + } else if (type === "environment"){ + return elem.environment.name === name; } else { return elem.environment.name === currentEnvironment.name; } diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index f2d29d288d8..a27e5a02cb4 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -75,7 +75,7 @@ const EnvironmentAliasesDataList = () => { open={infoOpened} onClose={() => setInfoOpened(false)} name={selectedInfo.name} - aliases={true} + type="aliases" /> } {data.map(item => ( diff --git a/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx b/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx index 303347ab053..6a9c1fe4f94 100644 --- a/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { i18n } from "@webiny/app/i18n"; import { DeleteIcon } from "@webiny/ui/List/DataList/icons"; import { useCrud } from "@webiny/app-admin/hooks/useCrud"; @@ -13,10 +13,12 @@ import { ListItemMeta, ListActions } from "@webiny/ui/List"; +import { ReactComponent as InformationIcon } from "../../icons/info.svg"; import { Link } from "@webiny/react-router"; import { ConfirmationDialogWithInput } from "./ConfirmationDialogWithInput"; - +import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; import styled from "@emotion/styled"; +import { css } from "emotion"; const t = i18n.ns("app-headless-cms/admin/environments/data-list"); @@ -29,6 +31,23 @@ const Wrapper = styled("div")({ } }); +const style = { + informationLabel: css({ + color: "var(--mdc-theme-primary)" + }), + icon: css({ + color: "rgba(255, 255, 255, 0.54)", + width: 16, + height: 16, + marginTop: "4px", + marginLeft: "10px" + }), + environmentText: css({ + display: "flex", + flexDirection: "row" + }) +}; + const getSeparator = (index, length) => { const lastIndex = length - 1; return index < lastIndex ? "," : ""; @@ -36,6 +55,10 @@ const getSeparator = (index, length) => { const EnvironmentsDataList = () => { const { actions, list } = useCrud(); + const [infoOpened, setInfoOpened] = useState(false); + const [selectedInfo, setSelectedInfo] = useState({ + name: "" + }); const { environments: { refreshEnvironments, selectAvailableEnvironment, isSelectedEnvironment } @@ -67,10 +90,31 @@ const EnvironmentsDataList = () => { {({ data, isSelected, select }) => { return ( + { + selectedInfo.name && + setInfoOpened(false)} + name={selectedInfo.name} + type="environment" + /> + } {data.map(item => ( select(item)}> - {item.name}{" "} +
    + {item.name}{" "} + +
    { + e.preventDefault(); + e.stopPropagation(); + setInfoOpened(true); + setSelectedInfo(item); + }}> + +
    +
    +
    {item.default && ( {t`(default)`} )} @@ -84,6 +128,7 @@ const EnvironmentsDataList = () => { {item.environmentAliases && item.environmentAliases.map((envAlias, index) => ( e.stopPropagation()} to={`/settings/cms/environments/aliases?id=${envAlias.id}`} title={t`This environment is linked with the "{environmentAlias}" alias.`( From 5ad1317df91744d919513db9500fd33a5bb97a82 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 9 Jul 2020 08:58:10 -0400 Subject: [PATCH 46/58] style(headless): add break with key to graphql url for proper spacing --- .../src/admin/components/ReactGraphqlUrl.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx b/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx index 31518b58455..cddc1126032 100644 --- a/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx +++ b/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx @@ -19,8 +19,8 @@ const style = { const ReactGraphqlUrl = () => { const { showSnackbar } = useSnackbar(); - return ( -
    + return ([ +
    This link allows you to access content created by different application across Webiny like Page Builder or Form Builder.}> GraphQL API: @@ -35,9 +35,10 @@ const ReactGraphqlUrl = () => { value={process.env.REACT_APP_GRAPHQL_API_URL} onCopy={() => showSnackbar("Successfully copied!")} /> -

    -
    - ) + +
    , +

    + ]) }; export default ReactGraphqlUrl; \ No newline at end of file From c9784c8f70ea49ce4c2fe1f35b7ea5a9bed0a141 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 9 Jul 2020 10:01:43 -0400 Subject: [PATCH 47/58] style(headless): run prettier for types in app admin --- packages/app-admin/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-admin/src/types.ts b/packages/app-admin/src/types.ts index 893f6289a0c..a184cd77b30 100644 --- a/packages/app-admin/src/types.ts +++ b/packages/app-admin/src/types.ts @@ -116,4 +116,4 @@ export type AdminInstallationPlugin = Plugin & { export type ApiInformationDialog = Plugin & { type: "admin-api-information-dialog"; render(props: { name: string; type: string }): React.ReactNode; -}; \ No newline at end of file +}; From 49063eb675a41bb5c0b3c9f4e5d55195163d3683 Mon Sep 17 00:00:00 2001 From: Emil Kais Date: Thu, 9 Jul 2020 10:26:16 -0400 Subject: [PATCH 48/58] wip(headless): fix build and runtime checks --- packages/app-admin/package.json | 1 - .../src/admin/components/ReactGraphqlUrl.tsx | 10 ++++------ .../src/admin/plugins/apiInformationDialog.tsx | 7 ++++++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/app-admin/package.json b/packages/app-admin/package.json index 3aeacf5cd0e..44e8f2b71c8 100644 --- a/packages/app-admin/package.json +++ b/packages/app-admin/package.json @@ -10,7 +10,6 @@ "author": "Pavel Denisjuk", "license": "MIT", "dependencies": { - "@apollo/react-hooks": "^3.1.5", "@babel/runtime": "^7.5.5", "@emotion/core": "^10.0.17", "@emotion/styled": "^10.0.17", diff --git a/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx b/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx index cddc1126032..59d627e142b 100644 --- a/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx +++ b/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx @@ -19,8 +19,8 @@ const style = { const ReactGraphqlUrl = () => { const { showSnackbar } = useSnackbar(); - return ([ -
    + return ( +
    This link allows you to access content created by different application across Webiny like Page Builder or Form Builder.}> GraphQL API: @@ -35,10 +35,8 @@ const ReactGraphqlUrl = () => { value={process.env.REACT_APP_GRAPHQL_API_URL} onCopy={() => showSnackbar("Successfully copied!")} /> - -
    , -

    - ]) +
    + ) }; export default ReactGraphqlUrl; \ No newline at end of file diff --git a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx index 6c2370f2221..1aab405fc2e 100644 --- a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx +++ b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx @@ -42,7 +42,12 @@ const plugin: ApiInformationDialog = { return (
    - {type === "api" && } + { + type === "api" && [ + , +

    + ] + } Headless CMS - {name} From bcca08f19ccdf54a7bfb93104bccf1908268bbb4 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 18:54:53 +0200 Subject: [PATCH 49/58] fix: remove @apollo/react-hooks --- packages/app-headless-cms/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/app-headless-cms/package.json b/packages/app-headless-cms/package.json index d0ec573fcc3..7fa36a8546d 100644 --- a/packages/app-headless-cms/package.json +++ b/packages/app-headless-cms/package.json @@ -13,7 +13,6 @@ ], "license": "MIT", "dependencies": { - "@apollo/react-hooks": "^3.1.5", "@babel/runtime": "^7.5.5", "@emotion/core": "^10.0.17", "@emotion/styled": "^10.0.17", From 3ec898a403a5503f9a13ff47adbcd927425cac0f Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 18:55:21 +0200 Subject: [PATCH 50/58] fix: rename ApiInformationDialog to ApiInformationDialogPlugin --- packages/app-admin/src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app-admin/src/types.ts b/packages/app-admin/src/types.ts index a184cd77b30..2aced8b33a0 100644 --- a/packages/app-admin/src/types.ts +++ b/packages/app-admin/src/types.ts @@ -113,7 +113,7 @@ export type AdminInstallationPlugin = Plugin & { render({ onInstalled }): React.ReactNode; }; -export type ApiInformationDialog = Plugin & { +export type ApiInformationDialogPlugin = Plugin & { type: "admin-api-information-dialog"; - render(props: { name: string; type: string }): React.ReactNode; + render(): React.ReactNode; }; From 22444a3e25b5eee0335b52632e4f9ff9055b769b Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:18:53 +0200 Subject: [PATCH 51/58] chore: remove old file --- .../src/admin/components/ReactGraphqlUrl.tsx | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx diff --git a/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx b/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx deleted file mode 100644 index 59d627e142b..00000000000 --- a/packages/app-headless-cms/src/admin/components/ReactGraphqlUrl.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import { css } from "emotion"; -import { CopyButton } from "@webiny/ui/Button"; -import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; -import { Typography } from "@webiny/ui/Typography"; -import { Tooltip } from "@webiny/ui/Tooltip"; - -const style = { - apiUrl: css({ - display: "flex", - alignItems: "center" - }), - api: css({ - fontWeight: "bold", - minWidth: "200px" - }) -}; - -const ReactGraphqlUrl = () => { - const { showSnackbar } = useSnackbar(); - - return ( -
    - This link allows you to access content created by different application across Webiny like Page Builder or Form Builder.}> - GraphQL API: - - - {process.env.REACT_APP_GRAPHQL_API_URL} - - showSnackbar("Successfully copied!")} - /> -
    - ) -}; - -export default ReactGraphqlUrl; \ No newline at end of file From 3a6f8d03563557dabcc4d7f030c0fc6164ca4e83 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:19:41 +0200 Subject: [PATCH 52/58] fix: create ApiUrlsDialog component --- .../src/admin/components/ApiUrlsDialog.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/app-headless-cms/src/admin/components/ApiUrlsDialog.tsx diff --git a/packages/app-headless-cms/src/admin/components/ApiUrlsDialog.tsx b/packages/app-headless-cms/src/admin/components/ApiUrlsDialog.tsx new file mode 100644 index 00000000000..bdffc48a803 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ApiUrlsDialog.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { css } from "emotion"; +import { i18n } from "@webiny/app/i18n"; +import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; +import HeadlessCmsApiUrls from "@webiny/app-headless-cms/admin/plugins/apiInformationDialog/HeadlessCmsApiUrls"; + +export type ApiUrlsDialogProps = { + open: boolean; + onClose: () => void; + name?: string; + type?: string; +}; + +const t = i18n.ns("app-headless-cms/admin/components/environment-selector-dialog"); + +const style = { + narrowDialog: css({ + ".mdc-dialog__surface": { + width: 800, + minWidth: 800 + } + }) +}; + +const ApiUrlsDialog: React.FC = ({ open, onClose, name, type }) => { + return ( + + {t`Environment: {name}`({ name: name })} + + + + + ); +}; + +export default ApiUrlsDialog; From cdad245d53816356c70badeaa31680effecd32b2 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:20:03 +0200 Subject: [PATCH 53/58] fix: create HeadlessCmsApiUrls component --- .../HeadlessCmsApiUrls.tsx | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 packages/app-headless-cms/src/admin/plugins/apiInformationDialog/HeadlessCmsApiUrls.tsx diff --git a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog/HeadlessCmsApiUrls.tsx b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog/HeadlessCmsApiUrls.tsx new file mode 100644 index 00000000000..73690434e46 --- /dev/null +++ b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog/HeadlessCmsApiUrls.tsx @@ -0,0 +1,114 @@ +import React, { useState } from "react"; +import { useQuery } from "react-apollo"; +import { css } from "emotion"; +import { CopyButton } from "@webiny/ui/Button"; +import { useCms } from "@webiny/app-headless-cms/admin/hooks"; +import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; +import { LIST_ENVIRONMENT_ALIASES } from "@webiny/app-headless-cms/admin/views/EnvironmentAliases/graphql"; +import { Typography } from "@webiny/ui/Typography"; +import { toLower } from "lodash"; +import get from "lodash/get"; + +const style = { + apiUrl: css({ + display: "flex", + alignItems: "center" + }), + aliasTitle: css({ + minWidth: "200px" + }), + aliasContainer: css({ + marginTop: "10px" + }) +}; + +const HeadlessCmsApiUrls = function({ name = null, type = null }) { + const { showSnackbar } = useSnackbar(); + const graphqlApiUrl = process.env.REACT_APP_API_URL; + const [totalAliases, setTotalAliases] = useState([]); + const { + environments: { currentEnvironment } + } = useCms(); + + useQuery(LIST_ENVIRONMENT_ALIASES, { + onCompleted: data => { + setTotalAliases(data.cms.environmentAliases.data); + } + }); + + return ( +
    + + Headless CMS - {name || get(currentEnvironment, "name")} + + {totalAliases + .filter(elem => { + if (type === "aliases") { + return elem.name === name; + } else if (type === "environment") { + return get(elem, "environment.name") === name; + } else { + return get(elem, "environment.name") === currentEnvironment.name; + } + }) + .map(elem => { + return ( +
    + Alias: {elem.name} +
    + + Content Delivery API: + + + {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`} + + showSnackbar("Successfully copied!")} + /> +
    +
    + + Content Preview API: + + + {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`} + + showSnackbar("Successfully copied!")} + /> +
    +
    + + Content Management API: + + + {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`} + + showSnackbar("Successfully copied!")} + /> +
    +

    +
    + ); + })} +
    + ); +}; + +export default HeadlessCmsApiUrls; From d7edc0a62b5c0bcddd2ef056f2989b8f16a78dd4 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:20:36 +0200 Subject: [PATCH 54/58] fix: use `HeadlessCmsApiUrls` component --- .../admin/plugins/apiInformationDialog.tsx | 128 +----------------- .../EnvironmentAliasesDataList.tsx | 6 +- .../Environments/EnvironmentsDataList.tsx | 4 +- 3 files changed, 13 insertions(+), 125 deletions(-) diff --git a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx index 1aab405fc2e..08e4349a335 100644 --- a/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx +++ b/packages/app-headless-cms/src/admin/plugins/apiInformationDialog.tsx @@ -1,127 +1,13 @@ -import React, { useState } from "react"; -import { useQuery } from '@apollo/react-hooks'; -import { css } from "emotion"; -import { ApiInformationDialog } from "@webiny/app-admin/types"; -import { CopyButton } from "@webiny/ui/Button"; -import { useCms } from "@webiny/app-headless-cms/admin/hooks"; -import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; -import { LIST_ENVIRONMENT_ALIASES } from "../views/EnvironmentAliases/graphql"; -import { Typography } from "@webiny/ui/Typography"; -import { toLower } from "lodash"; -import ReactGraphqlUrl from "@webiny/app-headless-cms/admin/components/ReactGraphqlUrl"; +import React from "react"; +import { ApiInformationDialogPlugin } from "@webiny/app-admin/types"; +import HeadlessCmsApiUrls from "./apiInformationDialog/HeadlessCmsApiUrls"; -const style = { - apiUrl: css({ - display: "flex", - alignItems: "center" - }), - aliasTitle: css({ - minWidth: "200px" - }), - aliasContainer: css({ - marginTop: "10px" - }) -}; - -const plugin: ApiInformationDialog = { +const plugin: ApiInformationDialogPlugin = { type: "admin-api-information-dialog", name: "admin-api-information-dialog-headless-cms", - render({ name, type }) { - const { showSnackbar } = useSnackbar(); - const graphqlApiUrl = process.env.REACT_APP_API_URL; - const [totalAliases, setTotalAliases] = useState([]); - const { - environments: { currentEnvironment } - } = useCms(); - - useQuery(LIST_ENVIRONMENT_ALIASES, { - onCompleted: data => { - setTotalAliases(data.cms.environmentAliases.data); - } - }); - - return ( -
    - { - type === "api" && [ - , -

    - ] - } - - Headless CMS - {name} - - { - totalAliases.filter((elem) => { - if (type === "aliases") { - return elem.name === name; - } else if (type === "environment"){ - return elem.environment.name === name; - } else { - return elem.environment.name === currentEnvironment.name; - } - }).map((elem) => { - return( -
    - - Alias: {elem.name} - -
    - - Content Delivery API: - - - {`${graphqlApiUrl}/cms/read/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -
    - - Content Preview API: - - - {`${graphqlApiUrl}/cms/preview/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -
    - - Content Management API: - - - {`${graphqlApiUrl}/cms/manage/${toLower(elem.name)}`} - - showSnackbar("Successfully copied!")} - /> -
    -

    -
    - ) - }) - } -
    - ) + render() { + return ; } }; -export default plugin; \ No newline at end of file +export default plugin; diff --git a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx index a27e5a02cb4..0542d09d119 100644 --- a/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/EnvironmentAliases/EnvironmentAliasesDataList.tsx @@ -6,7 +6,9 @@ import { ReactComponent as InformationIcon } from "../../icons/info.svg"; import { css } from "emotion"; import { useCrud } from "@webiny/app-admin/hooks/useCrud"; import { Typography } from "@webiny/ui/Typography"; -import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; +import ApiUrlsDialog from "@webiny/app-headless-cms/admin/components/ApiUrlsDialog"; + + import { DataList, List, @@ -71,7 +73,7 @@ const EnvironmentAliasesDataList = () => { { selectedInfo.name && - setInfoOpened(false)} name={selectedInfo.name} diff --git a/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx b/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx index 6a9c1fe4f94..bb45851cb25 100644 --- a/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx +++ b/packages/app-headless-cms/src/admin/views/Environments/EnvironmentsDataList.tsx @@ -16,9 +16,9 @@ import { import { ReactComponent as InformationIcon } from "../../icons/info.svg"; import { Link } from "@webiny/react-router"; import { ConfirmationDialogWithInput } from "./ConfirmationDialogWithInput"; -import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; import styled from "@emotion/styled"; import { css } from "emotion"; +import ApiUrlsDialog from "@webiny/app-headless-cms/admin/components/ApiUrlsDialog"; const t = i18n.ns("app-headless-cms/admin/environments/data-list"); @@ -92,7 +92,7 @@ const EnvironmentsDataList = () => { { selectedInfo.name && - setInfoOpened(false)} name={selectedInfo.name} From 757d906b076084cb94c01ed6661fef77e021d165 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:20:58 +0200 Subject: [PATCH 55/58] fix: simplify ApiInformationDialog component --- ...nfoDialog.tsx => ApiInformationDialog.tsx} | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) rename packages/app-admin/src/components/{EnvironmentInfoDialog.tsx => ApiInformationDialog.tsx} (53%) diff --git a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx b/packages/app-admin/src/components/ApiInformationDialog.tsx similarity index 53% rename from packages/app-admin/src/components/EnvironmentInfoDialog.tsx rename to packages/app-admin/src/components/ApiInformationDialog.tsx index bad4cd2a882..07c95d46f5d 100644 --- a/packages/app-admin/src/components/EnvironmentInfoDialog.tsx +++ b/packages/app-admin/src/components/ApiInformationDialog.tsx @@ -3,7 +3,7 @@ import { css } from "emotion"; import { i18n } from "@webiny/app/i18n"; import { Dialog, DialogTitle, DialogContent } from "@webiny/ui/Dialog"; import { getPlugins } from "@webiny/plugins"; -import { ApiInformationDialog } from "@webiny/app-admin/types"; +import { ApiInformationDialogPlugin } from "@webiny/app-admin/types"; export type NewContentModelDialogProps = { open: boolean; @@ -23,15 +23,8 @@ const style = { }) }; -const EnvironmentInfoDialog: React.FC = ({ - open, - onClose, - name, - type -}) => { - const adminInfoPlugins = getPlugins( - "admin-api-information-dialog" - ); +const ApiInformationDialog: React.FC = ({ open, onClose }) => { + const adminInfoPlugins = getPlugins("admin-api-information-dialog"); return ( = ({ className={style.narrowDialog} data-testid="environment-info-modal" > - {t`Environment: `}{name} + {t`API Information`} - { - adminInfoPlugins.map(pl => { - return ( -
    - {pl.render({ name, type })} -
    - ) - }) - } + {adminInfoPlugins.map(pl => ( +
    {pl.render()}
    + ))}
    - ) + ); }; -export default EnvironmentInfoDialog; \ No newline at end of file +export default ApiInformationDialog; From 3ebd4e772671e18d3016286c963353a24b4ecbe8 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:21:43 +0200 Subject: [PATCH 56/58] fix: remove properties --- packages/app-admin/src/components/ApiInformationDialog.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/app-admin/src/components/ApiInformationDialog.tsx b/packages/app-admin/src/components/ApiInformationDialog.tsx index 07c95d46f5d..26781897b69 100644 --- a/packages/app-admin/src/components/ApiInformationDialog.tsx +++ b/packages/app-admin/src/components/ApiInformationDialog.tsx @@ -8,8 +8,6 @@ import { ApiInformationDialogPlugin } from "@webiny/app-admin/types"; export type NewContentModelDialogProps = { open: boolean; onClose: () => void; - name: string; - type: string; }; const t = i18n.ns("app-admin/navigation"); From 8d989dfc7e2c85695429bed02b1ef10c328af8ab Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:22:21 +0200 Subject: [PATCH 57/58] fix: simplify "ApiInformationDialog" component --- .../src/plugins/Menu/Navigation/index.tsx | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx index b2bd7984d3f..c2c6afc82ad 100755 --- a/packages/app-admin/src/plugins/Menu/Navigation/index.tsx +++ b/packages/app-admin/src/plugins/Menu/Navigation/index.tsx @@ -14,37 +14,25 @@ import { ReactComponent as DocsIcon } from "@webiny/app-admin/assets/icons/icon- import { ReactComponent as CommunityIcon } from "@webiny/app-admin/assets/icons/icon-community.svg"; import { ReactComponent as GithubIcon } from "@webiny/app-admin/assets/icons/github-brands.svg"; import { ReactComponent as InfoIcon } from "@webiny/app-admin/assets/icons/info.svg"; -import EnvironmentInfoDialog from "@webiny/app-admin/components/EnvironmentInfoDialog"; +import ApiInformationDialog from "@webiny/app-admin/components/ApiInformationDialog"; import { i18n } from "@webiny/app/i18n"; const t = i18n.ns("app-admin/navigation"); -import get from "lodash.get"; - const style = { environmentContainer: css({ color: "var(--mdc-theme-text-secondary-on-background)" }), infoContainer: css({ - alignSelf: "center", + alignSelf: "center" }) }; -const getCurrentEnvironmentFromLocalStorage = () => { - try { - return JSON.parse(get(window, "localStorage.cms_environment")); - } catch { - return null; - } -}; - const Navigation = () => { const { hideMenu, menuIsShown, initSections } = useNavigation(); const [infoOpened, setInfoOpened] = useState(false); useEffect(initSections, []); - const currentEnvironment = getCurrentEnvironmentFromLocalStorage(); - const logo = useMemo(() => { const logoPlugin = getPlugin("admin-menu-logo"); if (logoPlugin) { @@ -78,7 +66,8 @@ const Navigation = () => { {menus} -
    { e.preventDefault(); e.stopPropagation(); @@ -87,7 +76,7 @@ const Navigation = () => { > - }/> + } /> {t`API information`} @@ -141,15 +130,8 @@ const Navigation = () => {
    - { - currentEnvironment && - setInfoOpened(false)} - name={currentEnvironment.name || t`N\A`} - type="api" - /> - } + + setInfoOpened(false)} />
    ); From 654a62f9e8c6041dc920b8c931da2b85aeebf0f8 Mon Sep 17 00:00:00 2001 From: Adrian Smijulj Date: Thu, 9 Jul 2020 19:22:37 +0200 Subject: [PATCH 58/58] fix: create "admin-api-information-dialog-graphql" plugin --- .../src/plugins/Menu/gqlApiInformation.tsx | 58 +++++++++++++++++++ packages/app-admin/src/plugins/Menu/index.tsx | 20 ++++--- 2 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 packages/app-admin/src/plugins/Menu/gqlApiInformation.tsx diff --git a/packages/app-admin/src/plugins/Menu/gqlApiInformation.tsx b/packages/app-admin/src/plugins/Menu/gqlApiInformation.tsx new file mode 100644 index 00000000000..d06b32e13a0 --- /dev/null +++ b/packages/app-admin/src/plugins/Menu/gqlApiInformation.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { ApiInformationDialogPlugin } from "@webiny/app-admin/types"; +import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; +import { Tooltip } from "@webiny/ui/Tooltip"; +import { Typography } from "@webiny/ui/Typography"; +import { CopyButton } from "@webiny/ui/Button"; +import { css } from "emotion"; + +const style = { + apiUrl: css({ + display: "flex", + alignItems: "center" + }), + api: css({ + fontWeight: "bold", + minWidth: "200px" + }) +}; + +const plugin: ApiInformationDialogPlugin = { + type: "admin-api-information-dialog", + name: "admin-api-information-dialog-graphql", + render() { + const { showSnackbar } = useSnackbar(); + + return ( + <> +
    + + This link allows you to access content created by different + application across Webiny like Page Builder or Form Builder. + + } + > + GraphQL API: + + + {process.env.REACT_APP_GRAPHQL_API_URL} + + showSnackbar("Successfully copied!")} + /> +
    +
    + + ); + } +}; + +export default plugin; diff --git a/packages/app-admin/src/plugins/Menu/index.tsx b/packages/app-admin/src/plugins/Menu/index.tsx index b53a5a2f7a6..9969f78f12c 100644 --- a/packages/app-admin/src/plugins/Menu/index.tsx +++ b/packages/app-admin/src/plugins/Menu/index.tsx @@ -1,13 +1,17 @@ import React from "react"; import Hamburger from "./Hamburger"; -import { AdminHeaderLeftPlugin } from "@webiny/app-admin/types"; +import gqlApiInformation from "./gqlApiInformation"; +import { AdminHeaderLeftPlugin, ApiInformationDialogPlugin } from "@webiny/app-admin/types"; -const plugin: AdminHeaderLeftPlugin = { - name: "admin-header-main-menu-icon", - type: "admin-header-left", - render() { - return ; - } -}; +const plugin = [ + { + name: "admin-header-main-menu-icon", + type: "admin-header-left", + render() { + return ; + } + } as AdminHeaderLeftPlugin, + gqlApiInformation as ApiInformationDialogPlugin +]; export default plugin;