[DEPR] feat!: remove notices wrapper (#731)

This commit is contained in:
Maxwell Frank
2025-11-13 12:19:12 -05:00
committed by GitHub
parent c38e80505c
commit 86b6574c60
15 changed files with 4 additions and 315 deletions

1
.env
View File

@@ -37,7 +37,6 @@ HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
ACCOUNT_SETTINGS_URL=''
ACCOUNT_PROFILE_URL=''
ENABLE_NOTICES=''
CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false

View File

@@ -43,7 +43,6 @@ HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
ACCOUNT_SETTINGS_URL='http://localhost:1997'
ACCOUNT_PROFILE_URL='http://localhost:1995'
ENABLE_NOTICES=''
CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=false
ENABLE_PROGRAMS=false

View File

@@ -42,7 +42,6 @@ HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
ACCOUNT_SETTINGS_URL='http://account-settings-url.test'
ACCOUNT_PROFILE_URL='http://account-profile-url.test'
ENABLE_NOTICES=''
CAREER_LINK_URL=''
ENABLE_EDX_PERSONAL_DASHBOARD=true
ENABLE_PROGRAMS=false

View File

@@ -67,7 +67,6 @@ module.exports = {
NEW_RELIC_LICENSE_KEY: '',
ACCOUNT_SETTINGS_URL: 'http://localhost:1997',
ACCOUNT_PROFILE_URL: 'http://localhost:1995',
ENABLE_NOTICES: '',
CAREER_LINK_URL: '',
EXPERIMENT_08_23_VAN_PAINTED_DOOR: true,
};

View File

@@ -1,25 +0,0 @@
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { logError, logInfo } from '@edx/frontend-platform/logging';
export const noticesUrl = `${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`;
export const getNotices = ({ onLoad, notFoundMessage }) => {
const authenticatedUser = getAuthenticatedUser();
const handleError = async (e) => {
// Error probably means that notices is not installed, which is fine.
const { customAttributes: { httpErrorStatus } } = e;
if (httpErrorStatus === 404) {
logInfo(`${e}. ${notFoundMessage}`);
} else {
logError(e);
}
};
if (authenticatedUser) {
return getAuthenticatedHttpClient().get(noticesUrl, {}).then(onLoad).catch(handleError);
}
return null;
};
export default { getNotices };

View File

@@ -1,65 +0,0 @@
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { logError, logInfo } from '@edx/frontend-platform/logging';
import * as api from './api';
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(() => ({
LMS_BASE_URL: 'test-lms-url',
})),
}));
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedHttpClient: jest.fn(),
getAuthenticatedUser: jest.fn(),
}));
jest.mock('@edx/frontend-platform/logging', () => ({
logError: jest.fn(),
logInfo: jest.fn(),
}));
const testData = 'test-data';
const successfulGet = () => Promise.resolve(testData);
const error404 = { customAttributes: { httpErrorStatus: 404 }, test: 'error' };
const error404Get = () => Promise.reject(error404);
const error500 = { customAttributes: { httpErrorStatus: 500 }, test: 'error' };
const error500Get = () => Promise.reject(error500);
const get = jest.fn().mockImplementation(successfulGet);
getAuthenticatedHttpClient.mockReturnValue({ get });
const authenticatedUser = { fake: 'user' };
getAuthenticatedUser.mockReturnValue(authenticatedUser);
const onLoad = jest.fn();
describe('getNotices api method', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('behavior', () => {
describe('not authenticated', () => {
it('does not fetch anything', () => {
getAuthenticatedUser.mockReturnValueOnce(null);
api.getNotices({ onLoad });
expect(get).not.toHaveBeenCalled();
});
});
describe('authenticated', () => {
it('fetches noticesUrl with onLoad behavior', async () => {
await api.getNotices({ onLoad });
expect(get).toHaveBeenCalledWith(api.noticesUrl, {});
expect(onLoad).toHaveBeenCalledWith(testData);
});
it('calls logInfo if fetch fails with 404', async () => {
get.mockImplementation(error404Get);
await api.getNotices({ onLoad });
expect(logInfo).toHaveBeenCalledWith(`${error404}. ${api.error404Message}`);
});
it('calls logError if fetch fails with non-404 error', async () => {
get.mockImplementation(error500Get);
await api.getNotices({ onLoad });
expect(logError).toHaveBeenCalledWith(error500);
});
});
});
});

View File

@@ -1,40 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { StrictDict } from 'utils';
import { getNotices } from './api';
import * as module from './hooks';
import messages from './messages';
/**
* This component uses the platform-plugin-notices plugin to function.
* If the user has an unacknowledged notice, they will be rerouted off
* course home and onto a full-screen notice page. If the plugin is not
* installed, or there are no notices, we just passthrough this component.
*/
export const state = StrictDict({
isRedirected: (val) => React.useState(val), // eslint-disable-line
});
export const useNoticesWrapperData = () => {
const [isRedirected, setIsRedirected] = module.state.isRedirected();
const { formatMessage } = useIntl();
React.useEffect(() => {
if (getConfig().ENABLE_NOTICES) {
getNotices({
onLoad: (data) => {
if (data?.data?.results?.length > 0) {
setIsRedirected(true);
window.location.replace(`${data.data.results[0]}?next=${window.location.href}`);
}
},
notFoundMessage: formatMessage(messages.error404Message),
});
}
}, [setIsRedirected, formatMessage]);
return { isRedirected };
};
export default useNoticesWrapperData;

View File

@@ -1,99 +0,0 @@
import React from 'react';
import { MockUseState, formatMessage } from 'testUtils';
import { getConfig } from '@edx/frontend-platform';
import { getNotices } from './api';
import * as hooks from './hooks';
jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn() }));
jest.mock('./api', () => ({ getNotices: jest.fn() }));
jest.mock('react', () => ({
...jest.requireActual('react'),
useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })),
useContext: jest.fn(context => context),
}));
jest.mock('@edx/frontend-platform/i18n', () => {
const { formatMessage: fn } = jest.requireActual('testUtils');
return {
...jest.requireActual('@edx/frontend-platform/i18n'),
useIntl: () => ({
formatMessage: fn,
}),
};
});
getConfig.mockReturnValue({ ENABLE_NOTICES: true });
const state = new MockUseState(hooks);
let hook;
describe('NoticesWrapper hooks', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('state hooks', () => {
state.testGetter(state.keys.isRedirected);
});
describe('useNoticesWrapperData', () => {
beforeEach(() => {
state.mock();
});
describe('behavior', () => {
it('initializes state hooks', () => {
hooks.useNoticesWrapperData();
expect(hooks.state.isRedirected).toHaveBeenCalledWith();
});
describe('effects', () => {
it('does not call notices if not enabled', () => {
getConfig.mockReturnValueOnce({ ENABLE_NOTICES: false });
hooks.useNoticesWrapperData();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
cb();
expect(getNotices).not.toHaveBeenCalled();
});
describe('getNotices call (if enabled) onLoad behavior', () => {
it('does not redirect if there are no results', () => {
hooks.useNoticesWrapperData();
expect(React.useEffect).toHaveBeenCalled();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
cb();
expect(getNotices).toHaveBeenCalled();
const { onLoad } = getNotices.mock.calls[0][0];
onLoad({});
expect(state.setState.isRedirected).not.toHaveBeenCalled();
onLoad({ data: {} });
expect(state.setState.isRedirected).not.toHaveBeenCalled();
onLoad({ data: { results: [] } });
expect(state.setState.isRedirected).not.toHaveBeenCalled();
});
it('redirects and set isRedirected if results are returned', () => {
delete window.location;
window.location = { replace: jest.fn(), href: 'test-old-href' };
hooks.useNoticesWrapperData();
const [cb, prereqs] = React.useEffect.mock.calls[0];
expect(prereqs).toEqual([state.setState.isRedirected, formatMessage]);
cb();
expect(getNotices).toHaveBeenCalled();
const { onLoad } = getNotices.mock.calls[0][0];
const target = 'url-target';
onLoad({ data: { results: [target] } });
expect(state.setState.isRedirected).toHaveBeenCalledWith(true);
expect(window.location.replace).toHaveBeenCalledWith(
`${target}?next=${window.location.href}`,
);
});
});
});
});
describe('output', () => {
it('forwards isRedirected from state call', () => {
hook = hooks.useNoticesWrapperData();
expect(hook.isRedirected).toEqual(state.stateVals.isRedirected);
});
});
});
});

View File

@@ -1,25 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import useNoticesWrapperData from './hooks';
/**
* This component uses the platform-plugin-notices plugin to function.
* If the user has an unacknowledged notice, they will be rerouted off
* course home and onto a full-screen notice page. If the plugin is not
* installed, or there are no notices, we just passthrough this component.
*/
const NoticesWrapper = ({ children }) => {
const { isRedirected } = useNoticesWrapperData();
return (
<div>
{isRedirected === true ? null : children}
</div>
);
};
NoticesWrapper.propTypes = {
children: PropTypes.node.isRequired,
};
export default NoticesWrapper;

View File

@@ -1,36 +0,0 @@
import { render, screen } from '@testing-library/react';
import useNoticesWrapperData from './hooks';
import NoticesWrapper from '.';
jest.mock('./hooks', () => jest.fn());
const hookProps = { isRedirected: false };
const children = [<b key={1}>some</b>, <i key={2}>children</i>];
describe('NoticesWrapper component', () => {
beforeEach(() => {
useNoticesWrapperData.mockClear();
});
describe('behavior', () => {
it('initializes hooks', () => {
useNoticesWrapperData.mockReturnValue(hookProps);
render(<NoticesWrapper>{children}</NoticesWrapper>);
expect(useNoticesWrapperData).toHaveBeenCalledWith();
});
});
describe('output', () => {
it('does not show children if redirected', () => {
useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true });
render(<NoticesWrapper>{children}</NoticesWrapper>);
expect(screen.queryByText('some')).not.toBeInTheDocument();
expect(screen.queryByText('children')).not.toBeInTheDocument();
});
it('shows children if not redirected', () => {
useNoticesWrapperData.mockReturnValue(hookProps);
render(<NoticesWrapper>{children}</NoticesWrapper>);
expect(screen.getByText('some')).toBeInTheDocument();
expect(screen.getByText('children')).toBeInTheDocument();
});
});
});

View File

@@ -1,11 +0,0 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
error404Message: {
id: 'learner-dash.notices.error404Message',
defaultMessage: 'This probably happened because the notices plugin is not installed on platform.',
description: 'Error message when notices API returns 404',
},
});
export default messages;

View File

@@ -14,7 +14,6 @@ const configuration = {
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '',
SUPPORT_URL: process.env.SUPPORT_URL || null,
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
CAREER_LINK_URL: process.env.CAREER_LINK_URL || null,
LOGO_URL: process.env.LOGO_URL,
ENABLE_EDX_PERSONAL_DASHBOARD: process.env.ENABLE_EDX_PERSONAL_DASHBOARD === 'true',

View File

@@ -27,7 +27,6 @@ import { configuration } from './config';
import messages from './i18n';
import App from './App';
import NoticesWrapper from './components/NoticesWrapper';
subscribe(APP_READY, () => {
const root = createRoot(document.getElementById('root'));
@@ -35,12 +34,10 @@ subscribe(APP_READY, () => {
root.render(
<StrictMode>
<AppProvider store={store}>
<NoticesWrapper>
<Routes>
<Route path="/" element={<PageWrap><App /></PageWrap>} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</NoticesWrapper>
<Routes>
<Route path="/" element={<PageWrap><App /></PageWrap>} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</AppProvider>
</StrictMode>,
);

View File

@@ -35,7 +35,6 @@ jest.mock('@edx/frontend-platform', () => ({
jest.mock('data/store', () => ({ redux: 'store' }));
jest.mock('./App', () => 'App');
jest.mock('components/NoticesWrapper', () => 'NoticesWrapper');
describe('app registry', () => {
let getElement;

View File

@@ -39,7 +39,6 @@ jest.unmock('reselect');
jest.unmock('hooks');
jest.mock('plugin-slots/WidgetSidebarSlot', () => jest.fn(() => 'widget-sidebar'));
jest.mock('components/NoticesWrapper', () => 'notices-wrapper');
jest.mock('@edx/frontend-platform', () => ({
...jest.requireActual('@edx/frontend-platform'),