refactor: rewrite useConfig with RQ
This commit is contained in:
@@ -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;
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -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,
|
||||
|
@@ -17,7 +17,6 @@ type Query {
|
||||
parameters: JSONObject
|
||||
): [SubstepArgument]
|
||||
getBillingAndUsage: GetBillingAndUsage
|
||||
getConfig(keys: [String]): JSONObject
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
|
@@ -42,7 +42,6 @@ const isAuthenticatedRule = rule()(isAuthenticated);
|
||||
export const authenticationRules = {
|
||||
Query: {
|
||||
'*': isAuthenticatedRule,
|
||||
getConfig: allow,
|
||||
},
|
||||
Mutation: {
|
||||
'*': isAuthenticatedRule,
|
||||
|
@@ -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 (
|
||||
<LogoImage
|
||||
data-test="custom-logo"
|
||||
|
@@ -15,7 +15,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useVersion from 'hooks/useVersion';
|
||||
import AppBar from 'components/AppBar';
|
||||
import Drawer from 'components/Drawer';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
import useAutomatischConfig from 'hooks/useAutomatischConfig';
|
||||
|
||||
const drawerLinks = [
|
||||
{
|
||||
@@ -77,11 +77,9 @@ const generateDrawerBottomLinks = async ({
|
||||
|
||||
export default function PublicLayout({ children }) {
|
||||
const version = useVersion();
|
||||
const { config, loading } = useConfig([
|
||||
'disableNotificationsPage',
|
||||
'additionalDrawerLink',
|
||||
'additionalDrawerLinkText',
|
||||
]);
|
||||
const { data: configData, isLoading } = useAutomatischConfig();
|
||||
const config = configData?.data;
|
||||
|
||||
const theme = useTheme();
|
||||
const formatMessage = useFormatMessage();
|
||||
const [bottomLinks, setBottomLinks] = React.useState([]);
|
||||
@@ -102,10 +100,10 @@ export default function PublicLayout({ children }) {
|
||||
setBottomLinks(newBottomLinks);
|
||||
}
|
||||
|
||||
if (loading) return;
|
||||
if (isLoading) return;
|
||||
|
||||
perform();
|
||||
}, [config, loading, version.newVersionCount]);
|
||||
}, [config, isLoading, version.newVersionCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@@ -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 <React.Fragment />;
|
||||
|
||||
if (isLoading && !logoSvgData) return <React.Fragment />;
|
||||
|
||||
if (logoSvgData) return <CustomLogo />;
|
||||
|
||||
return <DefaultLogo />;
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
@@ -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;
|
||||
|
@@ -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 }) => {
|
||||
</BaseThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
||||
|
@@ -1,6 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
export const GET_CONFIG = gql`
|
||||
query GetConfig($keys: [String]) {
|
||||
getConfig(keys: $keys)
|
||||
}
|
||||
`;
|
16
packages/web/src/hooks/useAutomatischConfig.js
Normal file
16
packages/web/src/hooks/useAutomatischConfig.js
Normal file
@@ -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;
|
||||
}
|
@@ -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,
|
||||
};
|
||||
}
|
@@ -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'],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@@ -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 (
|
||||
<ReactRouterRoutes>
|
||||
<Route
|
||||
@@ -147,4 +151,5 @@ function Routes() {
|
||||
</ReactRouterRoutes>
|
||||
);
|
||||
}
|
||||
|
||||
export default <Routes />;
|
||||
|
Reference in New Issue
Block a user