From 3a57349d8ad6ef904eeaac87af28a061637f8141 Mon Sep 17 00:00:00 2001 From: "kasia.oczkowska" Date: Thu, 21 Mar 2024 13:57:34 +0000 Subject: [PATCH] refactor: rewrite useConfig with RQ --- .../src/graphql/queries/get-config.ee.js | 32 ---- .../src/graphql/queries/get-config.ee.test.js | 140 ------------------ .../backend/src/graphql/query-resolvers.js | 2 - packages/backend/src/graphql/schema.graphql | 1 - .../backend/src/helpers/authentication.js | 1 - .../src/components/CustomLogo/index.ee.jsx | 10 +- packages/web/src/components/Layout/index.jsx | 14 +- packages/web/src/components/Logo/index.jsx | 13 +- .../src/components/MetadataProvider/index.jsx | 14 +- .../src/components/ThemeProvider/index.jsx | 6 +- .../web/src/graphql/queries/get-config.ee.js | 6 - .../web/src/hooks/useAutomatischConfig.js | 16 ++ packages/web/src/hooks/useConfig.js | 11 -- .../web/src/pages/UserInterface/index.jsx | 55 ++----- packages/web/src/routes.jsx | 9 +- 15 files changed, 76 insertions(+), 254 deletions(-) delete mode 100644 packages/backend/src/graphql/queries/get-config.ee.js delete mode 100644 packages/backend/src/graphql/queries/get-config.ee.test.js delete mode 100644 packages/web/src/graphql/queries/get-config.ee.js create mode 100644 packages/web/src/hooks/useAutomatischConfig.js delete mode 100644 packages/web/src/hooks/useConfig.js diff --git a/packages/backend/src/graphql/queries/get-config.ee.js b/packages/backend/src/graphql/queries/get-config.ee.js deleted file mode 100644 index 89cdd258..00000000 --- a/packages/backend/src/graphql/queries/get-config.ee.js +++ /dev/null @@ -1,32 +0,0 @@ -import appConfig from '../../config/app.js'; -import { hasValidLicense } from '../../helpers/license.ee.js'; -import Config from '../../models/config.js'; - -const getConfig = async (_parent, params) => { - if (!(await hasValidLicense())) return {}; - - const defaultConfig = { - disableNotificationsPage: appConfig.disableNotificationsPage, - disableFavicon: appConfig.disableFavicon, - additionalDrawerLink: appConfig.additionalDrawerLink, - additionalDrawerLinkText: appConfig.additionalDrawerLinkText, - }; - - const configQuery = Config.query(); - - if (Array.isArray(params.keys)) { - configQuery.whereIn('key', params.keys); - } - - const config = await configQuery.orderBy('key', 'asc'); - - return config.reduce((computedConfig, configEntry) => { - const { key, value } = configEntry; - - computedConfig[key] = value?.data; - - return computedConfig; - }, defaultConfig); -}; - -export default getConfig; diff --git a/packages/backend/src/graphql/queries/get-config.ee.test.js b/packages/backend/src/graphql/queries/get-config.ee.test.js deleted file mode 100644 index 4f7e6f5e..00000000 --- a/packages/backend/src/graphql/queries/get-config.ee.test.js +++ /dev/null @@ -1,140 +0,0 @@ -import { vi, describe, it, expect, beforeEach } from 'vitest'; -import request from 'supertest'; -import app from '../../app'; -import { createConfig } from '../../../test/factories/config'; -import appConfig from '../../config/app'; -import * as license from '../../helpers/license.ee'; - -describe('graphQL getConfig query', () => { - let configOne, configTwo, configThree, query; - - beforeEach(async () => { - configOne = await createConfig({ key: 'configOne' }); - configTwo = await createConfig({ key: 'configTwo' }); - configThree = await createConfig({ key: 'configThree' }); - - query = ` - query { - getConfig - } - `; - }); - - describe('and without valid license', () => { - beforeEach(async () => { - vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false); - }); - - describe('and correct permissions', () => { - it('should return empty config data', async () => { - const response = await request(app) - .post('/graphql') - .send({ query }) - .expect(200); - - const expectedResponsePayload = { data: { getConfig: {} } }; - - expect(response.body).toEqual(expectedResponsePayload); - }); - }); - }); - - describe('and with valid license', () => { - beforeEach(async () => { - vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); - }); - - describe('and without providing specific keys', () => { - it('should return all config data', async () => { - const response = await request(app) - .post('/graphql') - .send({ query }) - .expect(200); - - const expectedResponsePayload = { - data: { - getConfig: { - [configOne.key]: configOne.value.data, - [configTwo.key]: configTwo.value.data, - [configThree.key]: configThree.value.data, - disableNotificationsPage: false, - disableFavicon: false, - additionalDrawerLink: undefined, - additionalDrawerLinkText: undefined, - }, - }, - }; - - expect(response.body).toEqual(expectedResponsePayload); - }); - }); - - describe('and with providing specific keys', () => { - it('should return all config data', async () => { - query = ` - query { - getConfig(keys: ["configOne", "configTwo"]) - } - `; - - const response = await request(app) - .post('/graphql') - .send({ query }) - .expect(200); - - const expectedResponsePayload = { - data: { - getConfig: { - [configOne.key]: configOne.value.data, - [configTwo.key]: configTwo.value.data, - disableNotificationsPage: false, - disableFavicon: false, - additionalDrawerLink: undefined, - additionalDrawerLinkText: undefined, - }, - }, - }; - - expect(response.body).toEqual(expectedResponsePayload); - }); - }); - - describe('and with different defaults', () => { - beforeEach(async () => { - vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue( - true - ); - vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true); - vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue( - 'https://automatisch.io' - ); - vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue( - 'Automatisch' - ); - }); - - it('should return custom config', async () => { - const response = await request(app) - .post('/graphql') - .send({ query }) - .expect(200); - - const expectedResponsePayload = { - data: { - getConfig: { - [configOne.key]: configOne.value.data, - [configTwo.key]: configTwo.value.data, - [configThree.key]: configThree.value.data, - disableNotificationsPage: true, - disableFavicon: true, - additionalDrawerLink: 'https://automatisch.io', - additionalDrawerLinkText: 'Automatisch', - }, - }, - }; - - expect(response.body).toEqual(expectedResponsePayload); - }); - }); - }); -}); diff --git a/packages/backend/src/graphql/query-resolvers.js b/packages/backend/src/graphql/query-resolvers.js index b2926a29..55671f69 100644 --- a/packages/backend/src/graphql/query-resolvers.js +++ b/packages/backend/src/graphql/query-resolvers.js @@ -2,7 +2,6 @@ import getApp from './queries/get-app.js'; import getAppAuthClient from './queries/get-app-auth-client.ee.js'; import getAppAuthClients from './queries/get-app-auth-clients.ee.js'; import getBillingAndUsage from './queries/get-billing-and-usage.ee.js'; -import getConfig from './queries/get-config.ee.js'; import getConnectedApps from './queries/get-connected-apps.js'; import getDynamicData from './queries/get-dynamic-data.js'; import getDynamicFields from './queries/get-dynamic-fields.js'; @@ -15,7 +14,6 @@ const queryResolvers = { getAppAuthClient, getAppAuthClients, getBillingAndUsage, - getConfig, getConnectedApps, getDynamicData, getDynamicFields, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index a2d6c215..9f90abf5 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -17,7 +17,6 @@ type Query { parameters: JSONObject ): [SubstepArgument] getBillingAndUsage: GetBillingAndUsage - getConfig(keys: [String]): JSONObject } type Mutation { diff --git a/packages/backend/src/helpers/authentication.js b/packages/backend/src/helpers/authentication.js index 867aa33c..32bb2c9e 100644 --- a/packages/backend/src/helpers/authentication.js +++ b/packages/backend/src/helpers/authentication.js @@ -42,7 +42,6 @@ const isAuthenticatedRule = rule()(isAuthenticated); export const authenticationRules = { Query: { '*': isAuthenticatedRule, - getConfig: allow, }, Mutation: { '*': isAuthenticatedRule, diff --git a/packages/web/src/components/CustomLogo/index.ee.jsx b/packages/web/src/components/CustomLogo/index.ee.jsx index 7408d6dd..1c0acc0f 100644 --- a/packages/web/src/components/CustomLogo/index.ee.jsx +++ b/packages/web/src/components/CustomLogo/index.ee.jsx @@ -1,10 +1,14 @@ -import useConfig from 'hooks/useConfig'; +import useAutomatischConfig from 'hooks/useAutomatischConfig'; import { LogoImage } from './style.ee'; const CustomLogo = () => { - const { config, loading } = useConfig(['logo.svgData']); - if (loading || !config?.['logo.svgData']) return null; + const { data: configData, isLoading } = useAutomatischConfig(); + const config = configData?.data; + + if (isLoading || !config?.['logo.svgData']) return null; + const logoSvgData = config['logo.svgData']; + return ( diff --git a/packages/web/src/components/Logo/index.jsx b/packages/web/src/components/Logo/index.jsx index 363d2188..5f638e4e 100644 --- a/packages/web/src/components/Logo/index.jsx +++ b/packages/web/src/components/Logo/index.jsx @@ -1,12 +1,19 @@ import * as React from 'react'; + import CustomLogo from 'components/CustomLogo/index.ee'; import DefaultLogo from 'components/DefaultLogo'; -import useConfig from 'hooks/useConfig'; +import useAutomatischConfig from 'hooks/useAutomatischConfig'; + const Logo = () => { - const { config, loading } = useConfig(['logo.svgData']); + const { data: configData, isLoading } = useAutomatischConfig(); + const config = configData?.data; const logoSvgData = config?.['logo.svgData']; - if (loading && !logoSvgData) return ; + + if (isLoading && !logoSvgData) return ; + if (logoSvgData) return ; + return ; }; + export default Logo; diff --git a/packages/web/src/components/MetadataProvider/index.jsx b/packages/web/src/components/MetadataProvider/index.jsx index 19a549a4..b6453492 100644 --- a/packages/web/src/components/MetadataProvider/index.jsx +++ b/packages/web/src/components/MetadataProvider/index.jsx @@ -1,15 +1,22 @@ import * as React from 'react'; -import useConfig from 'hooks/useConfig'; + +import useAutomatischConfig from 'hooks/useAutomatischConfig'; + const MetadataProvider = ({ children }) => { - const { config } = useConfig(); + const { data: configData } = useAutomatischConfig(); + const config = configData?.data; + React.useEffect(() => { document.title = config?.title || 'Automatisch'; }, [config?.title]); + React.useEffect(() => { const existingFaviconElement = document.querySelector("link[rel~='icon']"); + if (config?.disableFavicon === true) { existingFaviconElement?.remove(); } + if (config?.disableFavicon === false) { if (existingFaviconElement) { existingFaviconElement.href = '/browser-tab.ico'; @@ -20,7 +27,10 @@ const MetadataProvider = ({ children }) => { newFaviconElement.href = '/browser-tab.ico'; } } + }, [config?.disableFavicon]); + return <>{children}; }; + export default MetadataProvider; diff --git a/packages/web/src/components/ThemeProvider/index.jsx b/packages/web/src/components/ThemeProvider/index.jsx index 66cc1a5f..ebdb882a 100644 --- a/packages/web/src/components/ThemeProvider/index.jsx +++ b/packages/web/src/components/ThemeProvider/index.jsx @@ -6,7 +6,7 @@ import set from 'lodash/set'; import * as React from 'react'; import useAutomatischInfo from 'hooks/useAutomatischInfo'; -import useConfig from 'hooks/useConfig'; +import useAutomatischConfig from 'hooks/useAutomatischConfig'; import { defaultTheme, mationTheme } from 'styles/theme'; const customizeTheme = (theme, config) => { @@ -28,7 +28,8 @@ const ThemeProvider = ({ children, ...props }) => { const { data: automatischInfo, isPending: isAutomatischInfoPending } = useAutomatischInfo(); const isMation = automatischInfo?.data.isMation; - const { config, loading: configLoading } = useConfig(); + const { data: configData, isLoading: configLoading } = useAutomatischConfig(); + const config = configData?.data; const customTheme = React.useMemo(() => { const installationTheme = isMation ? mationTheme : defaultTheme; @@ -51,4 +52,5 @@ const ThemeProvider = ({ children, ...props }) => { ); }; + export default ThemeProvider; diff --git a/packages/web/src/graphql/queries/get-config.ee.js b/packages/web/src/graphql/queries/get-config.ee.js deleted file mode 100644 index 02ff1ef9..00000000 --- a/packages/web/src/graphql/queries/get-config.ee.js +++ /dev/null @@ -1,6 +0,0 @@ -import { gql } from '@apollo/client'; -export const GET_CONFIG = gql` - query GetConfig($keys: [String]) { - getConfig(keys: $keys) - } -`; diff --git a/packages/web/src/hooks/useAutomatischConfig.js b/packages/web/src/hooks/useAutomatischConfig.js new file mode 100644 index 00000000..395f7b47 --- /dev/null +++ b/packages/web/src/hooks/useAutomatischConfig.js @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; +import api from 'helpers/api'; + +export default function useAutomatischConfig() { + const query = useQuery({ + queryKey: ['automatisch', 'config'], + queryFn: async ({ signal }) => { + const { data } = await api.get(`/v1/automatisch/config`, { + signal, + }); + return data; + }, + }); + + return query; +} diff --git a/packages/web/src/hooks/useConfig.js b/packages/web/src/hooks/useConfig.js deleted file mode 100644 index 72ad0909..00000000 --- a/packages/web/src/hooks/useConfig.js +++ /dev/null @@ -1,11 +0,0 @@ -import { useQuery } from '@apollo/client'; -import { GET_CONFIG } from 'graphql/queries/get-config.ee'; -export default function useConfig(keys) { - const { data, loading } = useQuery(GET_CONFIG, { - variables: { keys }, - }); - return { - config: data?.getConfig, - loading, - }; -} diff --git a/packages/web/src/pages/UserInterface/index.jsx b/packages/web/src/pages/UserInterface/index.jsx index 5c2cc0bd..cf5bdd19 100644 --- a/packages/web/src/pages/UserInterface/index.jsx +++ b/packages/web/src/pages/UserInterface/index.jsx @@ -3,43 +3,44 @@ import LoadingButton from '@mui/lab/LoadingButton'; import Grid from '@mui/material/Grid'; import Skeleton from '@mui/material/Skeleton'; import Stack from '@mui/material/Stack'; -import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar'; import merge from 'lodash/merge'; import * as React from 'react'; +import { useQueryClient } from '@tanstack/react-query'; + import ColorInput from 'components/ColorInput'; import Container from 'components/Container'; import Form from 'components/Form'; import PageTitle from 'components/PageTitle'; import TextField from 'components/TextField'; import { UPDATE_CONFIG } from 'graphql/mutations/update-config.ee'; -import { GET_CONFIG } from 'graphql/queries/get-config.ee'; import nestObject from 'helpers/nestObject'; -import useConfig from 'hooks/useConfig'; +import useAutomatischConfig from 'hooks/useAutomatischConfig'; import useFormatMessage from 'hooks/useFormatMessage'; +import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar'; import { primaryDarkColor, primaryLightColor, primaryMainColor, } from 'styles/theme'; + const getPrimaryMainColor = (color) => color || primaryMainColor; const getPrimaryDarkColor = (color) => color || primaryDarkColor; const getPrimaryLightColor = (color) => color || primaryLightColor; + const defaultValues = { title: 'Automatisch', 'palette.primary.main': primaryMainColor, 'palette.primary.dark': primaryDarkColor, 'palette.primary.light': primaryLightColor, }; + export default function UserInterface() { const formatMessage = useFormatMessage(); const [updateConfig, { loading }] = useMutation(UPDATE_CONFIG); - const { config, loading: configLoading } = useConfig([ - 'title', - 'palette.primary.main', - 'palette.primary.light', - 'palette.primary.dark', - 'logo.svgData', - ]); + const { data: configData, isLoading: configLoading } = useAutomatischConfig(); + const config = configData?.data; + const queryClient = useQueryClient(); + const enqueueSnackbar = useEnqueueSnackbar(); const configWithDefaults = merge({}, defaultValues, nestObject(config)); const handleUserInterfaceUpdate = async (uiData) => { @@ -64,37 +65,9 @@ export default function UserInterface() { optimisticResponse: { updateConfig: input, }, - update: async function (cache, { data: { updateConfig } }) { - const newConfigWithDefaults = merge({}, defaultValues, updateConfig); - cache.writeQuery({ - query: GET_CONFIG, - data: { - getConfig: newConfigWithDefaults, - }, - }); - cache.writeQuery({ - query: GET_CONFIG, - data: { - getConfig: newConfigWithDefaults, - }, - variables: { - keys: ['logo.svgData'], - }, - }); - cache.writeQuery({ - query: GET_CONFIG, - data: { - getConfig: newConfigWithDefaults, - }, - variables: { - keys: [ - 'title', - 'palette.primary.main', - 'palette.primary.light', - 'palette.primary.dark', - 'logo.svgData', - ], - }, + update: async function () { + queryClient.invalidateQueries({ + queryKey: ['automatisch', 'config'], }); }, }); diff --git a/packages/web/src/routes.jsx b/packages/web/src/routes.jsx index d9784daa..152b4e7a 100644 --- a/packages/web/src/routes.jsx +++ b/packages/web/src/routes.jsx @@ -1,4 +1,5 @@ import { Route, Routes as ReactRouterRoutes, Navigate } from 'react-router-dom'; + import Layout from 'components/Layout'; import NoResultFound from 'components/NotFound'; import PublicLayout from 'components/PublicLayout'; @@ -19,11 +20,14 @@ import * as URLS from 'config/urls'; import settingsRoutes from './settingsRoutes'; import adminSettingsRoutes from './adminSettingsRoutes'; import Notifications from 'pages/Notifications'; -import useConfig from 'hooks/useConfig'; +import useAutomatischConfig from 'hooks/useAutomatischConfig'; import useAuthentication from 'hooks/useAuthentication'; + function Routes() { - const { config } = useConfig(); + const { data: configData } = useAutomatischConfig(); const { isAuthenticated } = useAuthentication(); + const config = configData?.data; + return ( ); } + export default ;