diff --git a/public/locales/en.json b/public/locales/en.json
index 3c723272..bb60b2fc 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -1,4 +1,12 @@
{
+ "about": {
+ "browser-os": "Browser OS",
+ "browser-version": "Browser Version",
+ "copyright": "Copyright (c) {{year}} Red Hat Inc.",
+ "server-version": "Server Version",
+ "ui-version": "UI Version",
+ "username": "Username"
+ },
"form-dialog": {
"confirmation_heading_delete-scan": "Are you sure you want to delete the scan <0>{{name}}0>?",
"confirmation_heading_delete-credential": "Are you sure you want to delete the credential {{name}}?",
diff --git a/src/components/aboutModal/__tests__/__snapshots__/aboutModal.test.tsx.snap b/src/components/aboutModal/__tests__/__snapshots__/aboutModal.test.tsx.snap
new file mode 100644
index 00000000..e14e180a
--- /dev/null
+++ b/src/components/aboutModal/__tests__/__snapshots__/aboutModal.test.tsx.snap
@@ -0,0 +1,111 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AboutModal Component should attempt to display username, status data: username and status 1`] = `
+
+
+
+
+
+ t([
+ "about.username"
+])
+
+
+ lorem ipsum
+
+
+
+
+ t([
+ "about.ui-version"
+])
+
+
+ 0.0.0.0000000
+
+
+
+
+ t([
+ "about.server-version"
+])
+
+
+ 0.0.0.12345678
+
+
+
+
+
+`;
+
+exports[`AboutModal Component should render a basic component: basic 1`] = `
+
+
+
+
+
+ t([
+ "about.ui-version"
+])
+
+
+ 0.0.0.0000000
+
+
+
+
+
+`;
diff --git a/src/components/aboutModal/__tests__/aboutModal.test.tsx b/src/components/aboutModal/__tests__/aboutModal.test.tsx
new file mode 100644
index 00000000..5d688460
--- /dev/null
+++ b/src/components/aboutModal/__tests__/aboutModal.test.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { shallowComponent } from '../../../../config/jest.setupTests';
+import { AboutModal } from '../aboutModal';
+
+describe('AboutModal Component', () => {
+ it('should render a basic component', async () => {
+ const props = {};
+ const component = await shallowComponent();
+ expect(component).toMatchSnapshot('basic');
+ });
+
+ it('should attempt to display username, status data', async () => {
+ const mockGetStatus = jest.fn().mockResolvedValue({ server_version: '0.0.0.12345678' });
+ const mockUseStatusApi = jest.fn().mockReturnValue({ getStatus: mockGetStatus });
+ const mockGetUser = jest.fn().mockResolvedValue('lorem ipsum');
+ const mockUseUserApi = jest.fn().mockReturnValue({ getUser: mockGetUser });
+ const props = {
+ isOpen: true,
+ useUser: mockUseUserApi,
+ useStatus: mockUseStatusApi
+ };
+
+ const component = await shallowComponent();
+ expect(component).toMatchSnapshot('username and status');
+ });
+
+ it('should call onClose', async () => {
+ const mockOnClose = jest.fn();
+ const props = {
+ isOpen: true,
+ useUser: jest.fn().mockReturnValue({ getUser: jest.fn().mockResolvedValue('lorem ipsum') }),
+ useStatus: jest.fn().mockReturnValue({ getStatus: jest.fn().mockResolvedValue({}) }),
+ onClose: mockOnClose
+ };
+
+ render();
+
+ const user = userEvent.setup();
+ await user.click(screen.getByLabelText('Close Dialog'));
+
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/components/aboutModal/aboutModal.tsx b/src/components/aboutModal/aboutModal.tsx
new file mode 100644
index 00000000..3f14b224
--- /dev/null
+++ b/src/components/aboutModal/aboutModal.tsx
@@ -0,0 +1,102 @@
+import React, { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { AboutModal as PfAboutModal, TextContent, TextList, TextListItem } from '@patternfly/react-core';
+import { detect } from 'detect-browser';
+import moment from 'moment/moment';
+import { helpers } from '../../helpers';
+import { useUserApi } from '../../hooks/useLoginApi';
+import { useStatusApi, type ApiStatusSuccessType } from '../../hooks/useStatusApi';
+import backgroundImageSrc from '../../images/aboutBg.png';
+
+interface AboutModalProps {
+ currentYear?: string;
+ isOpen?: boolean;
+ onClose?: () => void;
+ titleImg?: string;
+ uiName?: string;
+ uiVersion?: string;
+ useStatus?: typeof useStatusApi;
+ useUser?: typeof useUserApi;
+}
+
+const AboutModal: React.FC = ({
+ currentYear = moment.utc(helpers.getCurrentDate()).format('YYYY'),
+ isOpen = false,
+ onClose = Function.prototype,
+ titleImg = helpers.getTitleImg(),
+ uiName = helpers.UI_NAME,
+ uiVersion = helpers.UI_VERSION,
+ useStatus = useStatusApi,
+ useUser = useUserApi
+}) => {
+ const { t } = useTranslation();
+ const { getStatus } = useStatus();
+ const { getUser } = useUser();
+ const [userName, setUserName] = useState();
+ const [stats, setStats] = useState();
+ const browser = detect();
+ const loadingClassName = (!stats && 'fadein') || '';
+
+ useEffect(() => {
+ if (isOpen && !stats) {
+ getUser().then(username => setUserName(username));
+ getStatus().then(
+ data => setStats(data),
+ error => console.error(`About status error: ${error} `)
+ );
+ }
+ }, [isOpen, getStatus, getUser, stats]);
+
+ return (
+ onClose()}
+ trademark={t('about.copyright', { year: currentYear })}
+ >
+
+
+ {userName && (
+
+ {t('about.username')}
+ {userName}
+
+ )}
+ {browser && (
+
+ {t('about.browser-version')}
+ {`${browser.name} ${browser.version}`}
+
+ )}
+ {browser && (
+
+ {t('about.browser-os')}
+ {browser.os || ''}
+
+ )}
+ {uiVersion && (
+
+ {t('about.ui-version')}
+ {uiVersion}
+
+ )}
+ {stats?.server_version && (
+
+
+ {t('about.server-version')}
+
+
+ {stats.server_version}
+
+
+ )}
+
+
+
+ );
+};
+
+export { AboutModal as default, AboutModal, type AboutModalProps };
diff --git a/src/components/viewLayout/__tests__/__snapshots__/viewLayoutToolbar.test.tsx.snap b/src/components/viewLayout/__tests__/__snapshots__/viewLayoutToolbar.test.tsx.snap
index 310d0abb..fc731fc7 100644
--- a/src/components/viewLayout/__tests__/__snapshots__/viewLayoutToolbar.test.tsx.snap
+++ b/src/components/viewLayout/__tests__/__snapshots__/viewLayoutToolbar.test.tsx.snap
@@ -12,70 +12,100 @@ exports[`ViewToolbar should attempt to load and display a username: user 1`] = `
`;
exports[`ViewToolbar should render a basic component: basic 1`] = `
-
-
+
-
-
-
-
-
-
- }
- isSelected={true}
- onChange={[Function]}
- />
-
-
-
+
+
+
+
+
+
+ }
+ isSelected={true}
+ onChange={[Function]}
+ />
+
+
+
+ }
+ onChange={[Function]}
+ />
+
+
+
+
-
-
-
+ toggle={[Function]}
+ >
+
+ About
+
+
+
+
+
- About
+ Logout
@@ -97,21 +128,15 @@ exports[`ViewToolbar should render a basic component: basic 1`] = `
-
-
-
-
- Logout
-
-
-
-
-
+
+
+
+
`;
diff --git a/src/components/viewLayout/__tests__/viewLayout.test.tsx b/src/components/viewLayout/__tests__/viewLayout.test.tsx
index 534ed7b8..d29f8687 100644
--- a/src/components/viewLayout/__tests__/viewLayout.test.tsx
+++ b/src/components/viewLayout/__tests__/viewLayout.test.tsx
@@ -14,7 +14,7 @@ describe('ViewLayout', () => {
it('should render a brand component', async () => {
const props = {
children: 'Lorem ipsum',
- isBrand: true,
+ titleImg: 'titleBrand.svg',
uiName: 'Discovery'
};
const component = await shallowComponent();
diff --git a/src/components/viewLayout/viewLayout.tsx b/src/components/viewLayout/viewLayout.tsx
index 0c401529..8346a4f3 100644
--- a/src/components/viewLayout/viewLayout.tsx
+++ b/src/components/viewLayout/viewLayout.tsx
@@ -28,18 +28,20 @@ import {
} from '@patternfly/react-core';
import { BarsIcon } from '@patternfly/react-icons';
import { helpers } from '../../helpers';
-import titleImg from '../../images/title.svg';
-import titleImgBrand from '../../images/titleBrand.svg';
import { IAppRoute, IAppRouteGroup, routes } from '../../routes';
import { AppToolbar } from './viewLayoutToolbar';
interface AppLayoutProps {
children: React.ReactNode;
- isBrand?: boolean;
+ titleImg?: string;
uiName?: string;
}
-const AppLayout: React.FC = ({ children, isBrand = helpers.UI_BRAND, uiName = helpers.UI_NAME }) => {
+const AppLayout: React.FC = ({
+ children,
+ titleImg = helpers.getTitleImg(),
+ uiName = helpers.UI_NAME
+}) => {
const { t } = useTranslation();
const [sidebarOpen, setSidebarOpen] = React.useState(true);
@@ -53,7 +55,7 @@ const AppLayout: React.FC = ({ children, isBrand = helpers.UI_BR
-
+
diff --git a/src/components/viewLayout/viewLayoutToolbar.tsx b/src/components/viewLayout/viewLayoutToolbar.tsx
index f306e55d..db725df3 100644
--- a/src/components/viewLayout/viewLayoutToolbar.tsx
+++ b/src/components/viewLayout/viewLayoutToolbar.tsx
@@ -22,6 +22,7 @@ import { EllipsisVIcon, MoonIcon, QuestionCircleIcon, SunIcon } from '@patternfl
import { useLogoutApi, useUserApi } from '../../hooks/useLoginApi';
import '@patternfly/react-styles/css/components/Avatar/avatar.css';
import './viewLayoutToolbar.css';
+import AboutModal from '../aboutModal/aboutModal';
interface AppToolbarProps {
useLogout?: typeof useLogoutApi;
@@ -33,6 +34,7 @@ const AppToolbar: React.FC = ({ useLogout = useLogoutApi, useUs
const { getUser } = useUser();
const [userName, setUserName] = useState();
const [helpOpen, setHelpOpen] = useState(false);
+ const [aboutOpen, setAboutOpen] = useState(false);
const [userDropdownOpen, setUserDropdownOpen] = useState(false);
const [kebabDropdownOpen, setKebabDropdownOpen] = useState(false);
const [isDarkTheme, setIsDarkTheme] = useState(
@@ -56,7 +58,9 @@ const AppToolbar: React.FC = ({ useLogout = useLogoutApi, useUs
};
applyTheme(isDarkTheme);
- const onAbout = () => {};
+ const onAbout = () => setAboutOpen(true);
+
+ const onAboutClose = () => setAboutOpen(false);
const onHelpSelect = (
_event: React.MouseEvent | undefined,
@@ -75,86 +79,114 @@ const AppToolbar: React.FC = ({ useLogout = useLogoutApi, useUs
};
return (
-
-
-
-
-
-
-
-
-
- }
- isSelected={!isDarkTheme}
- onChange={() => {
- setIsDarkTheme(false);
- applyTheme(false);
- }}
- />
-
-
-
- }
- isSelected={isDarkTheme}
- onChange={() => {
- setIsDarkTheme(true);
- applyTheme(true);
- }}
- />
-
-
-
+
+
+
+
+
+
+
+
+
+
+ }
+ isSelected={!isDarkTheme}
+ onChange={() => {
+ setIsDarkTheme(false);
+ applyTheme(false);
+ }}
+ />
+
+
+
+ }
+ isSelected={isDarkTheme}
+ onChange={() => {
+ setIsDarkTheme(true);
+ applyTheme(true);
+ }}
+ />
+
+
+
+ setHelpOpen(isOpen)}
+ isOpen={helpOpen}
+ toggle={toggleRef => (
+ setHelpOpen(prev => !prev)}
+ isExpanded={helpOpen}
+ >
+
+
+ )}
+ >
+
+ About
+
+
+
+
+
setHelpOpen(isOpen)}
- isOpen={helpOpen}
+ onSelect={onUserDropdownSelect}
+ onOpenChange={(isOpen: boolean) => setKebabDropdownOpen(isOpen)}
+ isOpen={kebabDropdownOpen}
toggle={toggleRef => (
setHelpOpen(prev => !prev)}
- isExpanded={helpOpen}
+ onClick={() => setKebabDropdownOpen(prev => !prev)}
+ isExpanded={kebabDropdownOpen}
+ style={{ width: 'auto' }}
+ data-ouia-component-id="user_dropdown_button"
>
-
+
)}
>
-
- About
+
+ Logout
-
+
setKebabDropdownOpen(isOpen)}
- isOpen={kebabDropdownOpen}
+ onOpenChange={(isOpen: boolean) => setUserDropdownOpen(isOpen)}
+ isOpen={userDropdownOpen}
toggle={toggleRef => (
setKebabDropdownOpen(prev => !prev)}
- isExpanded={kebabDropdownOpen}
- style={{ width: 'auto' }}
+ onClick={() => setUserDropdownOpen(prev => !prev)}
+ isExpanded={userDropdownOpen}
data-ouia-component-id="user_dropdown_button"
>
-
+
+
+ {userName}
+
)}
>
@@ -163,35 +195,10 @@ const AppToolbar: React.FC = ({ useLogout = useLogoutApi, useUs
-
-
- setUserDropdownOpen(isOpen)}
- isOpen={userDropdownOpen}
- toggle={toggleRef => (
- setUserDropdownOpen(prev => !prev)}
- isExpanded={userDropdownOpen}
- data-ouia-component-id="user_dropdown_button"
- >
-
-
- {userName}
-
-
- )}
- >
-
- Logout
-
-
-
-
-
+
+
+
+
);
};
diff --git a/src/helpers/__tests__/__snapshots__/helpers.test.ts.snap b/src/helpers/__tests__/__snapshots__/helpers.test.ts.snap
index bfc708c4..90c65678 100644
--- a/src/helpers/__tests__/__snapshots__/helpers.test.ts.snap
+++ b/src/helpers/__tests__/__snapshots__/helpers.test.ts.snap
@@ -11,6 +11,12 @@ exports[`getAuthType should return a credential type: credentialTypes 1`] = `
]
`;
+exports[`getCurrentDate should return a predictable current date: current date 1`] = `
+{
+ "currentDate": 2024-10-01T00:00:00.000Z,
+}
+`;
+
exports[`getTimeDisplayHowLongAgo should return a timestamp estimate: timestamps 1`] = `
[
"a few seconds ago",
@@ -18,3 +24,15 @@ exports[`getTimeDisplayHowLongAgo should return a timestamp estimate: timestamps
"a day ago",
]
`;
+
+exports[`getTitleImg should return a brand title image: brand title image 1`] = `
+{
+ "titleImg": "titleBrand.svg",
+}
+`;
+
+exports[`getTitleImg should return a title image: title image 1`] = `
+{
+ "titleImg": "title.svg",
+}
+`;
diff --git a/src/helpers/__tests__/helpers.test.ts b/src/helpers/__tests__/helpers.test.ts
index 7a2766a9..d47f984a 100644
--- a/src/helpers/__tests__/helpers.test.ts
+++ b/src/helpers/__tests__/helpers.test.ts
@@ -64,6 +64,25 @@ describe('getAuthType', () => {
});
});
+describe('getCurrentDate', () => {
+ it('should return a predictable current date', () => {
+ const currentDate = helpers.getCurrentDate();
+ expect({ currentDate }).toMatchSnapshot('current date');
+ });
+});
+
+describe('getTitleImg', () => {
+ it('should return a title image', () => {
+ const titleImg = helpers.getTitleImg();
+ expect({ titleImg }).toMatchSnapshot('title image');
+ });
+
+ it('should return a brand title image', () => {
+ const titleImg = helpers.getTitleImg(true);
+ expect({ titleImg }).toMatchSnapshot('brand title image');
+ });
+});
+
describe('noopTranslate', () => {
it('should format key, value, and components into a string', () => {
const key = 'testKey';
diff --git a/src/helpers/helpers.ts b/src/helpers/helpers.ts
index 473e3d23..9c59461e 100644
--- a/src/helpers/helpers.ts
+++ b/src/helpers/helpers.ts
@@ -7,6 +7,8 @@
*/
import React from 'react';
import moment, { type MomentInput } from 'moment';
+import titleImg from '../images/title.svg';
+import titleImgBrand from '../images/titleBrand.svg';
import { type CredentialType } from '../types/types';
/**
@@ -39,6 +41,12 @@ const UI_BRAND = process.env.REACT_APP_UI_BRAND === 'true';
*/
const UI_NAME = (UI_BRAND && process.env.REACT_APP_UI_BRAND_NAME) || `${process.env.REACT_APP_UI_NAME}`;
+/**
+ * UI packaged application version, with generated hash.
+ * See dotenv config files for updating. See build scripts for generated hash.
+ */
+const UI_VERSION = process.env.REACT_APP_UI_VERSION;
+
/**
* Generates a translation key for internationalization.
*
@@ -189,20 +197,38 @@ const downloadData = (data: string | ArrayBuffer | ArrayBufferView | Blob, fileN
const generateId = (prefix = 'generatedid') =>
`${prefix}-${(process.env.REACT_APP_ENV !== 'test' && Math.ceil(1e5 * Math.random())) || ''}`;
+/**
+ * Return a consistent current date
+ *
+ * @returns {string|Date}
+ */
+const getCurrentDate = () => (TEST_MODE && moment.utc('20241001').toDate()) || moment.utc().toDate();
+
+/**
+ * Return a consistent title image
+ *
+ * @param {boolean} isBrand
+ * @returns {string}
+ */
+const getTitleImg = (isBrand = UI_BRAND) => ((isBrand && titleImgBrand) || titleImg) as string;
+
const helpers = {
authType,
downloadData,
noopTranslate,
generateId,
getAuthType,
+ getCurrentDate,
getTimeDisplayHowLongAgo,
+ getTitleImg,
formatDate,
normalizeTotal,
DEV_MODE,
PROD_MODE,
TEST_MODE,
UI_BRAND,
- UI_NAME
+ UI_NAME,
+ UI_VERSION
};
export { helpers as default, helpers };
diff --git a/src/hooks/__tests__/__snapshots__/useStatusApi.test.ts.snap b/src/hooks/__tests__/__snapshots__/useStatusApi.test.ts.snap
new file mode 100644
index 00000000..bba30b6b
--- /dev/null
+++ b/src/hooks/__tests__/__snapshots__/useStatusApi.test.ts.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`useStatusApi should attempt an api call to get a status: apiCall 1`] = `
+[
+ [
+ "/api/v1/status/",
+ ],
+]
+`;
+
+exports[`useStatusApi should handle errors while attempting to get a status: getStatus, error 1`] = `
+{
+ "isAxiosError": true,
+ "message": "Mock error",
+}
+`;
+
+exports[`useStatusApi should handle success while attempting to get a status: getStatus, success 1`] = `undefined`;
+
+exports[`useStatusApi should process an API error response: callbackError 1`] = `
+{
+ "response": {
+ "data": {
+ "message": "Dolor sit",
+ },
+ },
+}
+`;
+
+exports[`useStatusApi should process an API success response: callbackSuccess 1`] = `
+{
+ "api_version": "lorem ipsum",
+ "server_version": "dolor sit",
+}
+`;
diff --git a/src/hooks/__tests__/useStatusApi.test.ts b/src/hooks/__tests__/useStatusApi.test.ts
new file mode 100644
index 00000000..be093f44
--- /dev/null
+++ b/src/hooks/__tests__/useStatusApi.test.ts
@@ -0,0 +1,60 @@
+import { renderHook } from '@testing-library/react';
+import axios from 'axios';
+import { useStatusApi } from '../useStatusApi';
+
+describe('useStatusApi', () => {
+ let hookResult;
+
+ beforeEach(() => {
+ const hook = renderHook(() => useStatusApi());
+ hookResult = hook?.result?.current;
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should attempt an api call to get a status', () => {
+ const { apiCall } = hookResult;
+ const spyAxios = jest.spyOn(axios, 'get');
+
+ apiCall();
+ expect(spyAxios.mock.calls).toMatchSnapshot('apiCall');
+ });
+
+ it('should handle success while attempting to get a status', async () => {
+ const { getStatus } = hookResult;
+ jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve({}));
+
+ await expect(getStatus()).resolves.toMatchSnapshot('getStatus, success');
+ });
+
+ it('should handle errors while attempting to get a status', async () => {
+ const { getStatus } = hookResult;
+ jest.spyOn(axios, 'get').mockImplementation(() => Promise.reject({ isAxiosError: true, message: 'Mock error' }));
+
+ await expect(getStatus()).rejects.toMatchSnapshot('getStatus, error');
+ });
+
+ it('should process an API success response', async () => {
+ const { callbackSuccess } = hookResult;
+
+ expect(callbackSuccess({ data: { api_version: 'lorem ipsum', server_version: 'dolor sit' } })).toMatchSnapshot(
+ 'callbackSuccess'
+ );
+ });
+
+ it('should process an API error response', async () => {
+ const { callbackError } = hookResult;
+
+ await expect(
+ callbackError({
+ response: {
+ data: {
+ message: 'Dolor sit'
+ }
+ }
+ })
+ ).rejects.toMatchSnapshot('callbackError');
+ });
+});
diff --git a/src/hooks/useStatusApi.ts b/src/hooks/useStatusApi.ts
new file mode 100644
index 00000000..4729a75d
--- /dev/null
+++ b/src/hooks/useStatusApi.ts
@@ -0,0 +1,52 @@
+import { useCallback } from 'react';
+import axios, { type AxiosError, type AxiosResponse, isAxiosError } from 'axios';
+import helpers from '../helpers';
+
+type ApiStatusSuccessType = {
+ server_version: string;
+};
+
+type ApiStatusErrorType = {
+ detail?: string;
+ message: string;
+};
+
+/**
+ * A status API call
+ */
+const useStatusApi = () => {
+ const apiCall = useCallback(
+ (): Promise> => axios.get(`${process.env.REACT_APP_STATUS_SERVICE}`),
+ []
+ );
+
+ const callbackSuccess = useCallback((response: AxiosResponse) => response?.data, []);
+
+ const callbackError = useCallback((error: AxiosError) => {
+ return Promise.reject(error);
+ }, []);
+
+ const getStatus = useCallback(async () => {
+ let response;
+ try {
+ response = await apiCall();
+ } catch (error) {
+ if (isAxiosError(error)) {
+ return callbackError(error);
+ }
+ if (!helpers.TEST_MODE) {
+ console.error(error);
+ }
+ }
+ return callbackSuccess(response);
+ }, [apiCall, callbackSuccess, callbackError]);
+
+ return {
+ apiCall,
+ callbackError,
+ callbackSuccess,
+ getStatus
+ };
+};
+
+export { useStatusApi, type ApiStatusSuccessType };
diff --git a/tests/__snapshots__/code.test.ts.snap b/tests/__snapshots__/code.test.ts.snap
index 80ec3335..fbb536dd 100644
--- a/tests/__snapshots__/code.test.ts.snap
+++ b/tests/__snapshots__/code.test.ts.snap
@@ -2,8 +2,9 @@
exports[`General code checks should only have specific console.[warn|log|info|error] methods: console methods 1`] = `
[
- "components/viewLayout/viewLayoutToolbar.tsx:65: console.log('selected', value);",
- "components/viewLayout/viewLayoutToolbar.tsx:73: console.log('selected', value);",
+ "components/aboutModal/aboutModal.tsx:45: error => console.error(\`About status error: \${error} \`)",
+ "components/viewLayout/viewLayoutToolbar.tsx:69: console.log('selected', value);",
+ "components/viewLayout/viewLayoutToolbar.tsx:77: console.log('selected', value);",
"helpers/queryHelpers.ts:70: console.log(\`Query: \`, query);",
"helpers/queryHelpers.ts:75: console.error(error);",
"hooks/useCredentialApi.ts:81: console.log(missingCredsMsg);",
@@ -28,6 +29,7 @@ exports[`General code checks should only have specific console.[warn|log|info|er
"hooks/useSourceApi.ts:127: console.error(error);",
"hooks/useSourceApi.ts:191: console.error(error);",
"hooks/useSourceApi.ts:255: console.error(error);",
+ "hooks/useStatusApi.ts:38: console.error(error);",
"views/scans/showScansModal.tsx:79: console.log({ aValue, bValue });",
"views/sources/addSourceModal.tsx:106: console.error(err);",
]