From 1c176d21eea8ab81f65edea4a364723e50378a31 Mon Sep 17 00:00:00 2001 From: Kostas Karvounis Date: Thu, 24 Nov 2022 13:10:57 +1100 Subject: [PATCH 1/6] [no-issue]: Move datetime to tsutils (#4266) --- packages/central-server/package.json | 1 + .../src/apiV2/surveyResponse.js | 2 +- packages/tsutils/package.json | 5 +++++ .../src/__tests__/datetime.test.ts} | 2 +- packages/tsutils/src/datetime.ts | 22 +++++++++++++++++++ packages/tsutils/src/index.ts | 1 + packages/utils/src/datetime.js | 18 --------------- packages/utils/src/index.js | 2 +- tupaia-packages.code-workspace | 1 + yarn.lock | 20 +++++++++++++++++ 10 files changed, 53 insertions(+), 21 deletions(-) rename packages/{utils/src/__tests__/datetime.test.js => tsutils/src/__tests__/datetime.test.ts} (91%) create mode 100644 packages/tsutils/src/datetime.ts diff --git a/packages/central-server/package.json b/packages/central-server/package.json index ecfea32edb..cf3b4fd906 100644 --- a/packages/central-server/package.json +++ b/packages/central-server/package.json @@ -33,6 +33,7 @@ "@tupaia/database": "1.0.0", "@tupaia/dhis-api": "1.0.0", "@tupaia/expression-parser": "1.0.0", + "@tupaia/tsutils": "1.0.0", "@tupaia/utils": "1.0.0", "adm-zip": "^0.5.5", "api-error-handler": "^1.0.0", diff --git a/packages/central-server/src/apiV2/surveyResponse.js b/packages/central-server/src/apiV2/surveyResponse.js index 1fee6f9e3a..394f6814f7 100644 --- a/packages/central-server/src/apiV2/surveyResponse.js +++ b/packages/central-server/src/apiV2/surveyResponse.js @@ -4,8 +4,8 @@ */ import keyBy from 'lodash.keyby'; +import { getTimezoneNameFromTimestamp } from '@tupaia/tsutils'; import { - getTimezoneNameFromTimestamp, ValidationError, MultiValidationError, ObjectValidator, diff --git a/packages/tsutils/package.json b/packages/tsutils/package.json index 9b7a5b8da5..06b4cb066a 100644 --- a/packages/tsutils/package.json +++ b/packages/tsutils/package.json @@ -22,7 +22,12 @@ "dependencies": { "@tupaia/utils": "1.0.0", "cookie": "^0.5.0", + "moment": "^2.24.0", + "moment-timezone": "^0.5.25", "puppeteer": "^15.4.0", "yup": "^0.32.9" + }, + "devDependencies": { + "@types/moment-timezone": "0.5.13" } } diff --git a/packages/utils/src/__tests__/datetime.test.js b/packages/tsutils/src/__tests__/datetime.test.ts similarity index 91% rename from packages/utils/src/__tests__/datetime.test.js rename to packages/tsutils/src/__tests__/datetime.test.ts index a1c836d410..e6a2df8922 100644 --- a/packages/utils/src/__tests__/datetime.test.js +++ b/packages/tsutils/src/__tests__/datetime.test.ts @@ -1,6 +1,6 @@ /** * Tupaia - * Copyright (c) 2017 - 2020 Beyond Essential Systems Pty Ltd + * Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd */ import moment from 'moment'; diff --git a/packages/tsutils/src/datetime.ts b/packages/tsutils/src/datetime.ts new file mode 100644 index 0000000000..de1652edb2 --- /dev/null +++ b/packages/tsutils/src/datetime.ts @@ -0,0 +1,22 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd + */ + +import moment from 'moment'; +import momentTimezone from 'moment-timezone'; + +/** + * @returns utcOffset in format: "+05:00" + */ +const getUtcOffsetFromTimestamp = (timestamp: string) => moment.parseZone(timestamp).format('Z'); + +/** + * @returns timezone name in format: "Pacific/Fiji". + */ +export const getTimezoneNameFromTimestamp = (timestamp: string) => + momentTimezone.tz + .names() + .find(name => getUtcOffsetFromTimestamp(timestamp) === momentTimezone.tz(name).format('Z')); + +export const utcMoment = (...args: Parameters) => moment.utc(...args); diff --git a/packages/tsutils/src/index.ts b/packages/tsutils/src/index.ts index 7a2a9c6f3f..bd733ef581 100644 --- a/packages/tsutils/src/index.ts +++ b/packages/tsutils/src/index.ts @@ -1,3 +1,4 @@ +export * from './datetime'; export * from './downloadPageAsPDF'; export * from './hashStringToInt'; export * from './typeGuards'; diff --git a/packages/utils/src/datetime.js b/packages/utils/src/datetime.js index 8475eee05b..431ad24cef 100644 --- a/packages/utils/src/datetime.js +++ b/packages/utils/src/datetime.js @@ -4,24 +4,6 @@ */ import moment from 'moment'; -import momentTimezone from 'moment-timezone'; - -/** - * @param {string} timestamp timestamp to get utcOffset from - * - * @returns {string} utcOffset in format: "+05:00" - */ -const getUtcOffsetFromTimestamp = timestamp => moment.parseZone(timestamp).format('Z'); - -/** - * @param {string} utcOffset utcOffset to match to timezone name - * - * @returns {string} timezone name in format: "Pacific/Fiji". - */ -export const getTimezoneNameFromTimestamp = timestamp => - momentTimezone.tz - .names() - .find(name => getUtcOffsetFromTimestamp(timestamp) === momentTimezone.tz(name).format('Z')); /** * @param {...any} args diff --git a/packages/utils/src/index.js b/packages/utils/src/index.js index a9c449c937..dde90560c6 100644 --- a/packages/utils/src/index.js +++ b/packages/utils/src/index.js @@ -8,7 +8,7 @@ export { DEFAULT_BINARY_OPTIONS, DEFAULT_BINARY_OPTIONS_OBJECT } from './constan export * from './compare'; export * from './authHeaderBuilders'; export * from './cypress'; -export { getTimezoneNameFromTimestamp, utcMoment } from './datetime'; +export { utcMoment } from './datetime'; export * from './legacyDhis'; export * from './errors'; export { Multilock } from './Multilock'; diff --git a/tupaia-packages.code-workspace b/tupaia-packages.code-workspace index f458d882cf..264829125c 100644 --- a/tupaia-packages.code-workspace +++ b/tupaia-packages.code-workspace @@ -150,6 +150,7 @@ "cleanup", "Codeship", "ctes", + "datetime", "debouncing", "deleters", "dhis", diff --git a/yarn.lock b/yarn.lock index 937915d2bc..367b037828 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5111,6 +5111,7 @@ __metadata: "@tupaia/database": 1.0.0 "@tupaia/dhis-api": 1.0.0 "@tupaia/expression-parser": 1.0.0 + "@tupaia/tsutils": 1.0.0 "@tupaia/utils": 1.0.0 adm-zip: ^0.5.5 api-error-handler: ^1.0.0 @@ -5637,7 +5638,10 @@ __metadata: resolution: "@tupaia/tsutils@workspace:packages/tsutils" dependencies: "@tupaia/utils": 1.0.0 + "@types/moment-timezone": 0.5.13 cookie: ^0.5.0 + moment: ^2.24.0 + moment-timezone: ^0.5.25 puppeteer: ^15.4.0 yup: ^0.32.9 languageName: unknown @@ -6355,6 +6359,15 @@ __metadata: languageName: node linkType: hard +"@types/moment-timezone@npm:0.5.13": + version: 0.5.13 + resolution: "@types/moment-timezone@npm:0.5.13" + dependencies: + moment: ">=2.14.0" + checksum: 35bd6414b790663e4879e38b4a344526ebe87b3f795929f9bc38f91f7caa373c65dd19a0e836182319f929d0f7fcff9b629efc6dbcef23d386bf0c87597f5b49 + languageName: node + linkType: hard + "@types/multer@npm:^1.4.7": version: 1.4.7 resolution: "@types/multer@npm:1.4.7" @@ -24123,6 +24136,13 @@ __metadata: languageName: node linkType: hard +"moment@npm:>=2.14.0": + version: 2.29.4 + resolution: "moment@npm:2.29.4" + checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e + languageName: node + linkType: hard + "moment@npm:^2.19.3, moment@npm:^2.29.1": version: 2.29.1 resolution: "moment@npm:2.29.1" From f4b5498424bd988740670f08d4b194cedf4bde9a Mon Sep 17 00:00:00 2001 From: Rohan Port <59544282+rohan-bes@users.noreply.github.com> Date: Thu, 24 Nov 2022 16:01:53 +1100 Subject: [PATCH 2/6] RN-429: Break out the LESMIS Admin Panel into a separate app (#4258) --- packages/admin-panel/src/VizBuilderApp/App.js | 9 +- .../admin-panel/src/VizBuilderApp/api/api.js | 2 +- .../Modal/SaveVisualisationModal.js | 8 +- .../src/VizBuilderApp/utils/index.js | 6 + .../utils/useVizBuilderBasePath.js | 11 ++ .../src/VizBuilderApp/views/CreateNew.js | 4 +- .../src/authentication/PrivateRoute.js | 6 +- packages/admin-panel/src/index.js | 37 ++++-- packages/admin-panel/src/library.js | 4 + packages/admin-panel/src/pages/LoginPage.js | 15 ++- packages/admin-panel/src/pages/LogoutPage.js | 6 +- packages/admin-panel/src/pages/index.js | 7 ++ .../src/pages/resources/DashboardItemsPage.js | 62 +++++----- .../src/pages/resources/MapOverlaysPage.js | 62 +++++----- packages/admin-panel/src/routes.js | 2 +- packages/admin-panel/src/sync/SyncStatus.js | 2 - .../src/utilities/AdminPanelProviders.js | 17 +-- .../src/utilities/VizBuilderProviders.js | 19 +-- packages/devops/configs/servers.conf | 2 +- packages/lesmis/package.json | 1 + packages/lesmis/src/api/api.js | 2 +- packages/lesmis/src/components/MainMenu.js | 48 +++----- .../{AdminPanelRoutes.js => AdminPanelApp.js} | 112 ++++++++++++------ .../lesmis/src/routes/LesmisAdminRoute.js | 11 +- packages/lesmis/src/routes/PageRoutes.js | 12 +- packages/lesmis/src/utils/getAdminApiUrl.js | 43 +++++++ packages/lesmis/src/utils/getApiUrl.js | 9 +- packages/lesmis/src/utils/index.js | 1 + packages/lesmis/src/utils/useAdminPanelUrl.js | 11 ++ .../views/AdminPanel/AdminPanelLoginPage.js | 22 ++++ .../src/views/AdminPanel/AdminPanelNavBar.js | 50 ++++++++ .../AdminPanel/AdminPanelProfileButton.js | 27 +++++ .../views/AdminPanel/DashboardItemsView.js | 18 --- yarn.lock | 1 + 34 files changed, 424 insertions(+), 225 deletions(-) create mode 100644 packages/admin-panel/src/VizBuilderApp/utils/index.js create mode 100644 packages/admin-panel/src/VizBuilderApp/utils/useVizBuilderBasePath.js create mode 100644 packages/admin-panel/src/pages/index.js rename packages/lesmis/src/routes/{AdminPanelRoutes.js => AdminPanelApp.js} (58%) create mode 100644 packages/lesmis/src/utils/getAdminApiUrl.js create mode 100644 packages/lesmis/src/utils/useAdminPanelUrl.js create mode 100644 packages/lesmis/src/views/AdminPanel/AdminPanelLoginPage.js create mode 100644 packages/lesmis/src/views/AdminPanel/AdminPanelNavBar.js create mode 100644 packages/lesmis/src/views/AdminPanel/AdminPanelProfileButton.js delete mode 100644 packages/lesmis/src/views/AdminPanel/DashboardItemsView.js diff --git a/packages/admin-panel/src/VizBuilderApp/App.js b/packages/admin-panel/src/VizBuilderApp/App.js index 8b98532736..16e8679ffa 100644 --- a/packages/admin-panel/src/VizBuilderApp/App.js +++ b/packages/admin-panel/src/VizBuilderApp/App.js @@ -11,6 +11,7 @@ import { Main } from './views/Main'; import { CreateNew } from './views/CreateNew'; import { useUser } from './api/queries'; import { VizConfigProvider as StateProvider } from './context'; +import { useVizBuilderBasePath } from './utils'; const Container = styled.main` display: flex; @@ -22,12 +23,14 @@ const Container = styled.main` export const App = ({ Navbar, Footer }) => { const { data, isLoading: isUserLoading, isBESAdmin } = useUser(); + const basePath = useVizBuilderBasePath(); + if (isUserLoading) { return ; } if (!isBESAdmin) { - return ; + return ; } const user = { ...data, name: `${data.firstName} ${data.lastName}` }; @@ -37,10 +40,10 @@ export const App = ({ Navbar, Footer }) => { {Navbar && } - + - +
diff --git a/packages/admin-panel/src/VizBuilderApp/api/api.js b/packages/admin-panel/src/VizBuilderApp/api/api.js index b2007aadd5..cdd259ffcc 100644 --- a/packages/admin-panel/src/VizBuilderApp/api/api.js +++ b/packages/admin-panel/src/VizBuilderApp/api/api.js @@ -7,7 +7,7 @@ import axios from 'axios'; import { saveAs } from 'file-saver'; import FetchError from './fetchError'; -const baseUrl = process.env.REACT_APP_API_URL; +const baseUrl = process.env.REACT_APP_VIZ_BUILDER_API_URL; const timeout = 45 * 1000; // 45 seconds // withCredentials needs to be set for cookies to save @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials diff --git a/packages/admin-panel/src/VizBuilderApp/components/Modal/SaveVisualisationModal.js b/packages/admin-panel/src/VizBuilderApp/components/Modal/SaveVisualisationModal.js index 4f331f27bd..fb35240e44 100644 --- a/packages/admin-panel/src/VizBuilderApp/components/Modal/SaveVisualisationModal.js +++ b/packages/admin-panel/src/VizBuilderApp/components/Modal/SaveVisualisationModal.js @@ -23,6 +23,7 @@ import { import { MODAL_STATUS, VIZ_TYPE_PARAM } from '../../constants'; import { useVizConfig, useVisualisation } from '../../context'; import { useSaveDashboardVisualisation, useSaveMapOverlayVisualisation } from '../../api'; +import { useVizBuilderBasePath } from '../../utils'; const TickIcon = styled(CheckCircle)` font-size: 2.5rem; @@ -41,6 +42,7 @@ export const SaveVisualisationModal = ({ isOpen, onClose }) => { const [_, { setVisualisationValue }] = useVizConfig(); const { visualisation } = useVisualisation(); + const basePath = useVizBuilderBasePath(); const { vizType } = useParams(); const useSaveViz = () => { @@ -71,11 +73,11 @@ export const SaveVisualisationModal = ({ isOpen, onClose }) => { onClose(); }); - let backLink = '/'; + let backLink = basePath; if (vizType === VIZ_TYPE_PARAM.DASHBOARD_ITEM) { - backLink = '/dashboard-items'; + backLink = backLink.concat('/visualisations'); } else if (vizType === VIZ_TYPE_PARAM.MAP_OVERLAY) { - backLink = '/dashboard-items/map-overlays'; + backLink = backLink.concat('/visualisations/map-overlays'); } if (status === MODAL_STATUS.SUCCESS) { diff --git a/packages/admin-panel/src/VizBuilderApp/utils/index.js b/packages/admin-panel/src/VizBuilderApp/utils/index.js new file mode 100644 index 0000000000..56afa65f0a --- /dev/null +++ b/packages/admin-panel/src/VizBuilderApp/utils/index.js @@ -0,0 +1,6 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd + */ + +export { useVizBuilderBasePath } from './useVizBuilderBasePath'; diff --git a/packages/admin-panel/src/VizBuilderApp/utils/useVizBuilderBasePath.js b/packages/admin-panel/src/VizBuilderApp/utils/useVizBuilderBasePath.js new file mode 100644 index 0000000000..d74943f14f --- /dev/null +++ b/packages/admin-panel/src/VizBuilderApp/utils/useVizBuilderBasePath.js @@ -0,0 +1,11 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd + */ + +import { useLocation } from 'react-router-dom'; + +export const useVizBuilderBasePath = () => { + const { pathname } = useLocation(); + return pathname.substring(0, pathname.indexOf('/viz-builder/')) || ''; +}; diff --git a/packages/admin-panel/src/VizBuilderApp/views/CreateNew.js b/packages/admin-panel/src/VizBuilderApp/views/CreateNew.js index 6ad5c0665c..5059193d50 100644 --- a/packages/admin-panel/src/VizBuilderApp/views/CreateNew.js +++ b/packages/admin-panel/src/VizBuilderApp/views/CreateNew.js @@ -11,6 +11,7 @@ import { Button, FlexEnd } from '@tupaia/ui-components'; import { DashboardMetadataForm } from '../components'; import { VIZ_TYPE_PARAM } from '../constants'; import { MapOverlayMetadataForm } from '../components/MapOverlay'; +import { useVizBuilderBasePath } from '../utils'; const Container = styled(MuiContainer)` flex: 1; @@ -58,9 +59,10 @@ export const CreateNew = () => { const { vizType } = useParams(); const history = useHistory(); + const basePath = useVizBuilderBasePath(); const handleCreate = () => { - history.push(`/viz-builder/${vizType}/`); + history.push(`${basePath}/viz-builder/${vizType}/`); }; const MetadataFormComponent = getMetadataFormComponent(vizType); diff --git a/packages/admin-panel/src/authentication/PrivateRoute.js b/packages/admin-panel/src/authentication/PrivateRoute.js index 319732f385..070aa147f0 100644 --- a/packages/admin-panel/src/authentication/PrivateRoute.js +++ b/packages/admin-panel/src/authentication/PrivateRoute.js @@ -12,7 +12,7 @@ import { getIsUserAuthenticated } from './selectors'; * A wrapper for that redirects to the login * screen if you're not yet authenticated. */ -export const PrivateRouteComponent = ({ isLoggedIn, children, ...props }) => { +export const PrivateRouteComponent = ({ loginPath, isLoggedIn, children, ...props }) => { return ( { ) : ( @@ -35,10 +35,12 @@ export const PrivateRouteComponent = ({ isLoggedIn, children, ...props }) => { PrivateRouteComponent.propTypes = { children: PropTypes.node.isRequired, isLoggedIn: PropTypes.bool, + loginPath: PropTypes.string, }; PrivateRouteComponent.defaultProps = { isLoggedIn: false, + loginPath: '/login', }; const mapStateToProps = state => ({ diff --git a/packages/admin-panel/src/index.js b/packages/admin-panel/src/index.js index 82e82a7ce5..da0bb5bce2 100644 --- a/packages/admin-panel/src/index.js +++ b/packages/admin-panel/src/index.js @@ -5,6 +5,9 @@ import React, { lazy, Suspense } from 'react'; import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import { render as renderReactApp } from 'react-dom'; +import { ThemeProvider } from 'styled-components'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import { MuiThemeProvider, StylesProvider } from '@material-ui/core/styles'; import 'react-table/react-table.css'; import { EnvBanner } from '@tupaia/ui-components'; import AdminPanel from './App'; @@ -13,6 +16,7 @@ import { AdminPanelProviders } from './utilities/AdminPanelProviders'; import { StoreProvider } from './utilities/StoreProvider'; import { Footer, Navbar } from './widgets'; import { TupaiaApi } from './api'; +import { theme } from './theme'; const VizBuilder = lazy(() => import('./VizBuilderApp')); @@ -24,19 +28,26 @@ renderReactApp( {/* Store provider applied above routes so that it always persists auth state */} - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + , diff --git a/packages/admin-panel/src/library.js b/packages/admin-panel/src/library.js index c0c6914b97..9acde56586 100644 --- a/packages/admin-panel/src/library.js +++ b/packages/admin-panel/src/library.js @@ -4,7 +4,11 @@ */ import 'react-table/react-table.css'; +export { LoginPage, LogoutPage } from './pages'; +export { PrivateRoute } from './authentication'; export * from './pages/resources'; export { IconButton } from './widgets'; export { AdminPanelDataProviders } from './utilities/AdminPanelProviders'; export { DataChangeAction } from './editor'; +export { App as VizBuilderApp } from './VizBuilderApp/App'; +export { VizBuilderProviders } from './utilities/VizBuilderProviders'; diff --git a/packages/admin-panel/src/pages/LoginPage.js b/packages/admin-panel/src/pages/LoginPage.js index c804e9951a..4430b7dded 100644 --- a/packages/admin-panel/src/pages/LoginPage.js +++ b/packages/admin-panel/src/pages/LoginPage.js @@ -59,15 +59,17 @@ const StyledLink = styled(MuiLink)` const requestAnAccountUrl = 'https://info.tupaia.org/contact'; -const LoginPageComponent = ({ isLoggedIn }) => { +const Logo = () => ; + +const LoginPageComponent = ({ isLoggedIn, redirectTo, LogoComponent }) => { if (isLoggedIn) { - return ; + return ; } return (
- + @@ -82,6 +84,13 @@ const LoginPageComponent = ({ isLoggedIn }) => { LoginPageComponent.propTypes = { isLoggedIn: PropTypes.bool.isRequired, + redirectTo: PropTypes.string, + LogoComponent: PropTypes.element, +}; + +LoginPageComponent.defaultProps = { + redirectTo: '/', + LogoComponent: Logo, }; const mapStateToProps = state => ({ diff --git a/packages/admin-panel/src/pages/LogoutPage.js b/packages/admin-panel/src/pages/LogoutPage.js index 28410b82c5..c8fc9d6e83 100644 --- a/packages/admin-panel/src/pages/LogoutPage.js +++ b/packages/admin-panel/src/pages/LogoutPage.js @@ -9,24 +9,26 @@ import { connect } from 'react-redux'; import { Redirect } from 'react-router-dom'; import { getIsUserAuthenticated, logout } from '../authentication'; -const LogoutPageComponent = ({ onLogout, isLoggedIn }) => { +const LogoutPageComponent = ({ redirectTo, onLogout, isLoggedIn }) => { useEffect(() => { onLogout(); }, [onLogout]); if (!isLoggedIn) { - return ; + return ; } return
Logging out...
; }; LogoutPageComponent.propTypes = { + redirectTo: PropTypes.string, isLoggedIn: PropTypes.bool, onLogout: PropTypes.func.isRequired, }; LogoutPageComponent.defaultProps = { + redirectTo: '/login', isLoggedIn: true, }; diff --git a/packages/admin-panel/src/pages/index.js b/packages/admin-panel/src/pages/index.js new file mode 100644 index 0000000000..d2cce369ed --- /dev/null +++ b/packages/admin-panel/src/pages/index.js @@ -0,0 +1,7 @@ +/** + * Tupaia + * Copyright (c) 2017 - 2022 Beyond Essential Systems Pty Ltd + */ + +export { LoginPage } from './LoginPage'; +export { LogoutPage } from './LogoutPage'; diff --git a/packages/admin-panel/src/pages/resources/DashboardItemsPage.js b/packages/admin-panel/src/pages/resources/DashboardItemsPage.js index b0d43bbadc..1f704f7613 100644 --- a/packages/admin-panel/src/pages/resources/DashboardItemsPage.js +++ b/packages/admin-panel/src/pages/resources/DashboardItemsPage.js @@ -49,33 +49,7 @@ const FIELDS = [ }, ]; -const IMPORT_CONFIG = { - title: 'Import Dashboard Visualisation', - subtitle: 'Please upload one or more .json files with visualisations to be imported:', - actionConfig: { - importEndpoint: 'dashboardVisualisations', - multiple: true, - }, - getFinishedMessage: response => ( - <> - {response.message} - {response.importedVizes.map(({ code, id }) => ( -

- {`${code}: `} - View in Visualisation Builder -

- ))} - - ), -}; - -const renderNewDashboardVizButton = () => ( - - }>New - -); - -export const DashboardItemsPage = ({ getHeaderEl, isBESAdmin, ...props }) => { +export const DashboardItemsPage = ({ getHeaderEl, isBESAdmin, vizBuilderBaseUrl, ...props }) => { const extraEditFields = [ // ID field for constructing viz-builder path only, not for showing or editing { @@ -90,7 +64,7 @@ export const DashboardItemsPage = ({ getHeaderEl, isBESAdmin, ...props }) => { editConfig: { type: 'link', linkOptions: { - path: '/viz-builder/dashboard-item/:id', + path: `${vizBuilderBaseUrl}/viz-builder/dashboard-item/:id`, parameters: { id: 'id' }, }, visibilityCriteria: { @@ -131,12 +105,40 @@ export const DashboardItemsPage = ({ getHeaderEl, isBESAdmin, ...props }) => { }, ]; + const importConfig = { + title: 'Import Dashboard Visualisation', + subtitle: 'Please upload one or more .json files with visualisations to be imported:', + actionConfig: { + importEndpoint: 'dashboardVisualisations', + multiple: true, + }, + getFinishedMessage: response => ( + <> + {response.message} + {response.importedVizes.map(({ code, id }) => ( +

+ {`${code}: `} + + View in Visualisation Builder + +

+ ))} + + ), + }; + + const renderNewDashboardVizButton = () => ( + + }>New + + ); + return ( { DashboardItemsPage.propTypes = { getHeaderEl: PropTypes.func.isRequired, isBESAdmin: PropTypes.bool, + vizBuilderBaseUrl: PropTypes.string, }; DashboardItemsPage.defaultProps = { isBESAdmin: false, + vizBuilderBaseUrl: '', }; diff --git a/packages/admin-panel/src/pages/resources/MapOverlaysPage.js b/packages/admin-panel/src/pages/resources/MapOverlaysPage.js index 5a283cee1b..4c9ddad8e4 100644 --- a/packages/admin-panel/src/pages/resources/MapOverlaysPage.js +++ b/packages/admin-panel/src/pages/resources/MapOverlaysPage.js @@ -112,33 +112,7 @@ const FIELDS = [ }, ]; -const IMPORT_CONFIG = { - title: 'Import Map Overlay Visualisation', - subtitle: 'Please upload one or more .json files with visualisations to be imported:', - actionConfig: { - importEndpoint: 'mapOverlayVisualisations', - multiple: true, - }, - getFinishedMessage: response => ( - <> - {response.message} - {response.importedVizes.map(({ code, id }) => ( -

- {`${code}: `} - View in Visualisation Builder -

- ))} - - ), -}; - -const renderNewMapOverlayVizButton = () => ( - - }>New - -); - -export const MapOverlaysPage = ({ getHeaderEl, isBESAdmin, ...props }) => { +export const MapOverlaysPage = ({ getHeaderEl, isBESAdmin, vizBuilderBaseUrl, ...props }) => { const extraEditFields = [ // ID field for constructing viz-builder path only, not for showing or editing { @@ -153,7 +127,7 @@ export const MapOverlaysPage = ({ getHeaderEl, isBESAdmin, ...props }) => { editConfig: { type: 'link', linkOptions: { - path: '/viz-builder/map-overlay/:id', + path: `${vizBuilderBaseUrl}/viz-builder/map-overlay/:id`, parameters: { id: 'id' }, }, visibilityCriteria: { @@ -194,12 +168,40 @@ export const MapOverlaysPage = ({ getHeaderEl, isBESAdmin, ...props }) => { }, ]; + const importConfig = { + title: 'Import Map Overlay Visualisation', + subtitle: 'Please upload one or more .json files with visualisations to be imported:', + actionConfig: { + importEndpoint: 'mapOverlayVisualisations', + multiple: true, + }, + getFinishedMessage: response => ( + <> + {response.message} + {response.importedVizes.map(({ code, id }) => ( +

+ {`${code}: `} + + View in Visualisation Builder + +

+ ))} + + ), + }; + + const renderNewMapOverlayVizButton = () => ( + + }>New + + ); + return ( { MapOverlaysPage.propTypes = { getHeaderEl: PropTypes.func.isRequired, isBESAdmin: PropTypes.bool, + vizBuilderBaseUrl: PropTypes.string, }; MapOverlaysPage.defaultProps = { isBESAdmin: false, + vizBuilderBaseUrl: '', }; diff --git a/packages/admin-panel/src/routes.js b/packages/admin-panel/src/routes.js index e21dccb7e7..db46c0dbd0 100644 --- a/packages/admin-panel/src/routes.js +++ b/packages/admin-panel/src/routes.js @@ -87,7 +87,7 @@ export const ROUTES = [ }, { label: 'Visualisations', - to: '/dashboard-items', + to: '/visualisations', icon: , tabs: [ { diff --git a/packages/admin-panel/src/sync/SyncStatus.js b/packages/admin-panel/src/sync/SyncStatus.js index be433079d1..5b84b610ba 100644 --- a/packages/admin-panel/src/sync/SyncStatus.js +++ b/packages/admin-panel/src/sync/SyncStatus.js @@ -64,8 +64,6 @@ const SpinningSyncIcon = styled(SyncIcon)` const SyncingIconButton = styled(IconButton)` display: flex; - background-color: ${props => props.theme.palette.blue[100]}; - &.Mui-disabled { background-color: ${props => props.theme.palette.primary.main}; color: white; diff --git a/packages/admin-panel/src/utilities/AdminPanelProviders.js b/packages/admin-panel/src/utilities/AdminPanelProviders.js index dd67ed5669..b25d7e8812 100644 --- a/packages/admin-panel/src/utilities/AdminPanelProviders.js +++ b/packages/admin-panel/src/utilities/AdminPanelProviders.js @@ -4,11 +4,7 @@ */ import React from 'react'; -import { MuiThemeProvider, StylesProvider } from '@material-ui/core/styles'; -import { ThemeProvider } from 'styled-components'; -import CssBaseline from '@material-ui/core/CssBaseline'; import { StoreProvider } from './StoreProvider'; -import { theme } from '../theme'; import { TupaiaApi } from '../api'; import { ApiProvider } from './ApiProvider'; @@ -16,18 +12,7 @@ import { ApiProvider } from './ApiProvider'; export const AdminPanelProviders = ({ children }) => { const api = new TupaiaApi(); - return ( - - - - - - {children} - - - - - ); + return {children}; }; // For use in external apps such as LESMIS diff --git a/packages/admin-panel/src/utilities/VizBuilderProviders.js b/packages/admin-panel/src/utilities/VizBuilderProviders.js index 4eb400aa4b..914bfdfecc 100644 --- a/packages/admin-panel/src/utilities/VizBuilderProviders.js +++ b/packages/admin-panel/src/utilities/VizBuilderProviders.js @@ -4,26 +4,15 @@ */ import React from 'react'; -import { ThemeProvider } from 'styled-components'; import { QueryClient, QueryClientProvider } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; -import { MuiThemeProvider, StylesProvider } from '@material-ui/core/styles'; -import CssBaseline from '@material-ui/core/CssBaseline'; -import { theme } from '../theme'; const queryClient = new QueryClient(); // eslint-disable-next-line react/prop-types export const VizBuilderProviders = ({ children }) => ( - - - - - - - {children} - - - - + + + {children} + ); diff --git a/packages/devops/configs/servers.conf b/packages/devops/configs/servers.conf index 368a7d379a..6b0add422a 100644 --- a/packages/devops/configs/servers.conf +++ b/packages/devops/configs/servers.conf @@ -236,7 +236,7 @@ server { listen 80; listen [::]:80; - server_name ~^.*admin-api\.tupaia\.org$; + server_name admin-api.lesmis.la ~^.*admin-api\.tupaia\.org$; root /usr/share/nginx/html; # Redirect to HTTPs when behind a proxy diff --git a/packages/lesmis/package.json b/packages/lesmis/package.json index e0f7a17637..27f4e22714 100755 --- a/packages/lesmis/package.json +++ b/packages/lesmis/package.json @@ -47,6 +47,7 @@ "react-hook-form": "^6.15.1", "react-password-strength-bar": "^0.3.3", "react-query": "^3.9.6", + "react-redux": "^5.0.6", "react-router-dom": "^5.2.0", "styled-components": "^5.1.0" }, diff --git a/packages/lesmis/src/api/api.js b/packages/lesmis/src/api/api.js index 8bd7402fbe..a51631c113 100644 --- a/packages/lesmis/src/api/api.js +++ b/packages/lesmis/src/api/api.js @@ -36,7 +36,7 @@ const request = async (endpoint, options) => { const requestOptions = getRequestOptions(options); try { - const response = await axios(`${getApiUrl()}/v1/${endpoint}`, requestOptions); + const response = await axios(`${getApiUrl()}/${endpoint}`, requestOptions); return response.data; } catch (error) { // normalise errors using fetch error class diff --git a/packages/lesmis/src/components/MainMenu.js b/packages/lesmis/src/components/MainMenu.js index 8d669d0847..8ebc8e4444 100644 --- a/packages/lesmis/src/components/MainMenu.js +++ b/packages/lesmis/src/components/MainMenu.js @@ -18,7 +18,15 @@ import { ListItemText, ListSubheader, } from '@material-ui/core'; -import { Home, ImportContacts, ContactMail, Close, Menu, Assignment } from '@material-ui/icons'; +import { + Home, + ImportContacts, + ContactMail, + Close, + Menu, + Assignment, + Build, +} from '@material-ui/icons'; import { LightIconButton } from '@tupaia/ui-components'; import { LocaleListItemLink } from './LocaleLinks'; import { FlexEnd } from './Layout'; @@ -85,35 +93,13 @@ const TupaiaText = styled(Typography)` line-height: 140%; `; -/* eslint-disable */ -const AdminPanelLinks = () => ( - <> - Admin - - - - - - - - - - - - - - - - - - - - - - - - - +const AdminPanelLink = () => ( + + + + + + ); export const MainMenu = () => { @@ -161,7 +147,7 @@ export const MainMenu = () => { } /> - {isLesmisAdmin && } + {isLesmisAdmin && } diff --git a/packages/lesmis/src/routes/AdminPanelRoutes.js b/packages/lesmis/src/routes/AdminPanelApp.js similarity index 58% rename from packages/lesmis/src/routes/AdminPanelRoutes.js rename to packages/lesmis/src/routes/AdminPanelApp.js index 82c9431531..ae6cb6865a 100644 --- a/packages/lesmis/src/routes/AdminPanelRoutes.js +++ b/packages/lesmis/src/routes/AdminPanelApp.js @@ -3,44 +3,47 @@ * Copyright (c) 2017 - 2021 Beyond Essential Systems Pty Ltd */ import React from 'react'; -import styled from 'styled-components'; +import PropTypes from 'prop-types'; import { Switch, Redirect, Route, useRouteMatch } from 'react-router-dom'; -import { TabsToolbar } from '@tupaia/ui-components'; +import { connect } from 'react-redux'; import { Assignment, InsertChart, PeopleAlt } from '@material-ui/icons'; +import { TabsToolbar } from '@tupaia/ui-components'; import { DashboardsPage, QuestionsPage, SurveysPage, + SyncGroupsPage, DataElementsPage, + DashboardItemsPage, DashboardRelationsPage, MapOverlayGroupRelationsPage, MapOverlayGroupsPage, MapOverlaysPage, UsersPage, - AdminPanelDataProviders, + LogoutPage, + PrivateRoute, + VizBuilderProviders, + VizBuilderApp, } from '@tupaia/admin-panel'; import { LesmisAdminRoute } from './LesmisAdminRoute'; -import { useUser } from '../api/queries'; -import { getApiUrl } from '../utils/getApiUrl'; import { PermissionsView } from '../views/AdminPanel/PermissionsView'; -import { DashboardItemsView } from '../views/AdminPanel/DashboardItemsView'; import { ApprovedSurveyResponsesView, DraftSurveyResponsesView, RejectedSurveyResponsesView, NonApprovalSurveyResponsesView, } from '../views/AdminPanel/SurveyResponsesView'; +import { AdminPanelNavbar } from '../views/AdminPanel/AdminPanelNavBar'; +import { AdminPanelLoginPage } from '../views/AdminPanel/AdminPanelLoginPage'; +import { useAdminPanelUrl } from '../utils'; // Only show users who signed up through lesmis const UsersView = props => ; -// Hide the new button until there is a viz builder in lesmis -const MapOverlaysView = props => ; - -export const ROUTES = [ +const getRoutes = adminUrl => [ { label: 'Survey Data', - to: '/survey-responses', + to: `${adminUrl}/survey-responses`, icon: , tabs: [ { @@ -67,7 +70,7 @@ export const ROUTES = [ }, { label: 'Surveys', - to: '/surveys', + to: `${adminUrl}/surveys`, icon: , tabs: [ { @@ -85,17 +88,22 @@ export const ROUTES = [ to: '/data-elements', component: DataElementsPage, }, + { + label: 'Sync Groups', + to: '/sync-groups', + component: SyncGroupsPage, + }, ], }, { label: 'Visualisations', - to: '/visualisations', + to: `${adminUrl}/visualisations`, icon: , tabs: [ { label: 'Dashboard Items', to: '', - component: DashboardItemsView, + component: props => , }, { label: 'Dashboards', @@ -110,7 +118,7 @@ export const ROUTES = [ { label: 'Map Overlays', to: '/map-overlays', - component: MapOverlaysView, + component: props => , }, { label: 'Map Overlay Groups', @@ -126,7 +134,7 @@ export const ROUTES = [ }, { label: 'Users & Permissions', - to: '/users', + to: `${adminUrl}/users`, icon: , tabs: [ { @@ -143,46 +151,76 @@ export const ROUTES = [ }, ]; -const config = { apiUrl: `${getApiUrl()}/admin` }; - -const HeaderContainer = styled.div` - background: ${props => props.theme.palette.primary.main}; - border-top: 1px solid rgba(0, 0, 0, 0.2); ; -`; - -const AdminPanelRoutes = () => { +const AdminPanelApp = ({ user, isBESAdmin }) => { const headerEl = React.useRef(null); const { path } = useRouteMatch(); - const { isLesmisAdmin } = useUser(); + const adminUrl = useAdminPanelUrl(); const getHeaderEl = () => { return headerEl; }; + const routes = getRoutes(adminUrl); + return ( - -
- + + + + + + + + + + } + /> + + + + +
- {[...ROUTES].map(route => ( - + {[...routes].map(route => ( + {route.tabs.map(tab => ( - - + + ))} - + ))} -
- +
+ +
); }; -// Must be a default export as React.lazy currently only supports default exports. -export default AdminPanelRoutes; +AdminPanelApp.propTypes = { + user: PropTypes.shape({ + name: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + firstName: PropTypes.string, + profileImage: PropTypes.string, + }).isRequired, + isBESAdmin: PropTypes.bool, +}; + +AdminPanelApp.defaultProps = { + isBESAdmin: false, +}; + +export default connect( + state => ({ + user: state?.authentication?.user || {}, + isBESAdmin: state?.authentication?.isBESAdmin || false, + }), + null, +)(AdminPanelApp); diff --git a/packages/lesmis/src/routes/LesmisAdminRoute.js b/packages/lesmis/src/routes/LesmisAdminRoute.js index c25487eb9c..6e7d76ae4e 100644 --- a/packages/lesmis/src/routes/LesmisAdminRoute.js +++ b/packages/lesmis/src/routes/LesmisAdminRoute.js @@ -5,17 +5,10 @@ */ import React from 'react'; import { Route } from 'react-router-dom'; -import { useUser } from '../api/queries'; -import { FullPageLoader } from '../components'; import { NotAuthorisedView } from '../views/NotAuthorisedView'; -export const LesmisAdminRoute = props => { - const { isLoading: isUserLoading, isLesmisAdmin } = useUser(); - if (isUserLoading) { - return ; - } - - if (!isLesmisAdmin) { +export const LesmisAdminRoute = ({ isBESAdmin = false, ...props }) => { + if (!isBESAdmin) { return ; } diff --git a/packages/lesmis/src/routes/PageRoutes.js b/packages/lesmis/src/routes/PageRoutes.js index 44140f8395..0c5448aa2a 100644 --- a/packages/lesmis/src/routes/PageRoutes.js +++ b/packages/lesmis/src/routes/PageRoutes.js @@ -5,6 +5,7 @@ */ import React, { lazy, Suspense } from 'react'; import { Switch, Route, useRouteMatch } from 'react-router-dom'; +import { AdminPanelDataProviders } from '@tupaia/admin-panel'; import { NavBar, Footer, FullPageLoader } from '../components'; import { HomeView } from '../views/HomeView'; import { ProfileView } from '../views/ProfileView'; @@ -17,8 +18,10 @@ import { NotAuthorisedView } from '../views/NotAuthorisedView'; import { VerifyEmailView } from '../views/VerifyEmailView'; import { ABOUT_PAGE, FQS_PAGE, CONTACT_PAGE } from '../constants'; import { ExportView, PDF_DOWNLOAD_VIEW } from '../views/ExportView'; +import { getAdminApiUrl } from '../utils/getAdminApiUrl'; -const AdminPanel = lazy(() => import('./AdminPanelRoutes')); +const AdminPanel = lazy(() => import('./AdminPanelApp')); +const adminPanelConfig = { apiUrl: `${getAdminApiUrl()}` }; /** * Main Page Routes @@ -49,9 +52,10 @@ export const PageRoutes = React.memo(() => {