From a6755e2eee0cc7ee7912595903c09cde2486dd7f Mon Sep 17 00:00:00 2001 From: Kyle McCormick Date: Sun, 27 Nov 2022 19:14:08 -0500 Subject: [PATCH] fix: use getConfig in order to support runtime configuration Before, gradebook was reading config from `process.env` directly, which locked the app into using only static (build-time) configuration. In order to enable dynamic (runtime) configuration, we update gradebook to use frontend-platform's standard configuration interface: `mergeConfig()` and `getConfig()`. Bump version from 1.5.0 to 1.6.0. (I would normally just do a patch release for a fix, but the version was hasn't been bumped for a while, so adding in full runtime configuration support seemed like it warranted a proper minor version bump.) --- package.json | 2 +- src/components/GradebookHeader/index.jsx | 4 ++-- src/config/index.js | 16 ---------------- src/data/services/lms/urls.js | 4 ++-- src/data/store.js | 4 ++-- src/data/store.test.js | 14 +++++++------- src/index.jsx | 17 +++++++++++++++++ src/index.test.jsx | 5 ++++- src/segment.js | 4 ++-- 9 files changed, 37 insertions(+), 33 deletions(-) delete mode 100644 src/config/index.js diff --git a/package.json b/package.json index 31e4cbf6..e59c83a0 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@edx/frontend-app-gradebook", - "version": "1.5.0", + "version": "1.6.0", "description": "edx editable gradebook-ui to manipulate grade overrides on subsections", "repository": { "type": "git", diff --git a/src/components/GradebookHeader/index.jsx b/src/components/GradebookHeader/index.jsx index 466620ac..1b004cd4 100644 --- a/src/components/GradebookHeader/index.jsx +++ b/src/components/GradebookHeader/index.jsx @@ -2,10 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { getConfig } from '@edx/frontend-platform'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { Button } from '@edx/paragon'; -import { configuration } from 'config'; import { views } from 'data/constants/app'; import actions from 'data/actions'; import selectors from 'data/selectors'; @@ -25,7 +25,7 @@ export class GradebookHeader extends React.Component { } lmsInstructorDashboardUrl = courseId => ( - `${configuration.LMS_BASE_URL}/courses/${courseId}/instructor` + `${getConfig().LMS_BASE_URL}/courses/${courseId}/instructor` ); handleToggleViewClick() { diff --git a/src/config/index.js b/src/config/index.js deleted file mode 100644 index 1ed328b9..00000000 --- a/src/config/index.js +++ /dev/null @@ -1,16 +0,0 @@ -const configuration = { - BASE_URL: process.env.BASE_URL, - LMS_BASE_URL: process.env.LMS_BASE_URL, - LOGIN_URL: process.env.LOGIN_URL, - LOGOUT_URL: process.env.LOGOUT_URL, - CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH, - REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT, - DATA_API_BASE_URL: process.env.DATA_API_BASE_URL, - SECURE_COOKIES: process.env.NODE_ENV !== 'development', - SEGMENT_KEY: process.env.SEGMENT_KEY, - ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME, -}; - -const features = {}; - -export { configuration, features }; diff --git a/src/data/services/lms/urls.js b/src/data/services/lms/urls.js index df09cc53..843dc99a 100644 --- a/src/data/services/lms/urls.js +++ b/src/data/services/lms/urls.js @@ -1,9 +1,9 @@ +import { getConfig } from '@edx/frontend-platform'; import { StrictDict } from 'utils'; -import { configuration } from 'config'; import { historyRecordLimit } from './constants'; import { filterQuery, stringifyUrl } from './utils'; -const baseUrl = `${configuration.LMS_BASE_URL}`; +const baseUrl = `${getConfig().LMS_BASE_URL}`; const courseId = window.location.pathname.split('/').filter(Boolean).pop() || ''; diff --git a/src/data/store.js b/src/data/store.js index 09474a97..dfb73b44 100755 --- a/src/data/store.js +++ b/src/data/store.js @@ -4,19 +4,19 @@ import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProductio import { createLogger } from 'redux-logger'; import { createMiddleware } from 'redux-beacon'; import Segment from '@redux-beacon/segment'; +import { getConfig } from '@edx/frontend-platform'; import actions from './actions'; import selectors from './selectors'; import reducers from './reducers'; import eventsMap from './services/segment/mapping'; -import { configuration } from '../config'; export const createStore = () => { const loggerMiddleware = createLogger(); const middleware = [thunkMiddleware, loggerMiddleware]; // Conditionally add the segmentMiddleware only if the SEGMENT_KEY environment variable exists. - if (configuration.SEGMENT_KEY) { + if (getConfig().SEGMENT_KEY) { middleware.push(createMiddleware(eventsMap, Segment())); } const store = redux.createStore( diff --git a/src/data/store.test.js b/src/data/store.test.js index f0d3f7bb..0d0860db 100644 --- a/src/data/store.test.js +++ b/src/data/store.test.js @@ -4,12 +4,12 @@ import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProductio import { createLogger } from 'redux-logger'; import { createMiddleware } from 'redux-beacon'; import Segment from '@redux-beacon/segment'; +import { getConfig } from '@edx/frontend-platform'; import actions from './actions'; import selectors from './selectors'; import reducers from './reducers'; import eventsMap from './services/segment/mapping'; -import { configuration } from '../config'; import exportedStore, { createStore } from './store'; @@ -22,10 +22,10 @@ jest.mock('redux-logger', () => ({ createLogger: () => 'logger', })); jest.mock('redux-thunk', () => 'thunkMiddleware'); -jest.mock('../config', () => ({ - configuration: { +jest.mock('@edx/frontend-platform', () => ({ + getConfig: () => ({ SEGMENT_KEY: 'a-fake-segment-key', - }, + }), })); jest.mock('redux-beacon', () => ({ createMiddleware: jest.fn((map, model) => ({ map, model })), @@ -60,9 +60,9 @@ describe('store aggregator module', () => { }); }); describe('if no SEGMENT_KEY', () => { - const key = configuration.SEGMENT_KEY; + const key = getConfig().SEGMENT_KEY; beforeEach(() => { - configuration.SEGMENT_KEY = false; + getConfig().SEGMENT_KEY = false; }); it('exports thunk and logger middleware, composed and applied with dev tools', () => { expect(createStore().middleware).toEqual( @@ -70,7 +70,7 @@ describe('store aggregator module', () => { ); }); afterEach(() => { - configuration.SEGMENT_KEY = key; + getConfig().SEGMENT_KEY = key; }); }); }); diff --git a/src/index.jsx b/src/index.jsx index bb9d1c0c..925c196e 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -7,6 +7,7 @@ import ReactDOM from 'react-dom'; import { APP_READY, initialize, + mergeConfig, subscribe, } from '@edx/frontend-platform'; import { messages as headerMessages } from '@edx/frontend-component-header'; @@ -20,6 +21,22 @@ subscribe(APP_READY, () => { }); initialize({ + handlers: { + config: () => { + mergeConfig({ + BASE_URL: process.env.BASE_URL, + LMS_BASE_URL: process.env.LMS_BASE_URL, + LOGIN_URL: process.env.LOGIN_URL, + LOGOUT_URL: process.env.LOGOUT_URL, + CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH, + REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT, + DATA_API_BASE_URL: process.env.DATA_API_BASE_URL, + SECURE_COOKIES: process.env.NODE_ENV !== 'development', + SEGMENT_KEY: process.env.SEGMENT_KEY, + ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME, + }); + }, + }, messages: [ appMessages, headerMessages, diff --git a/src/index.test.jsx b/src/index.test.jsx index 645c8e27..900fb635 100644 --- a/src/index.test.jsx +++ b/src/index.test.jsx @@ -46,10 +46,13 @@ describe('app registry', () => { ReactDOM.render(, document.getElementById('root')), ); }); - test('initialize is called with footerMessages and requireAuthenticatedUser', () => { + test('initialize is called with requireAuthenticatedUser, messages, and a config handler', () => { expect(initialize).toHaveBeenCalledWith({ messages: [appMessages, headerMessages, footerMessages], requireAuthenticatedUser: true, + handlers: { + config: expect.any(Function), + }, }); }); }); diff --git a/src/segment.js b/src/segment.js index c2eb562a..309b20b0 100644 --- a/src/segment.js +++ b/src/segment.js @@ -1,6 +1,6 @@ // The code in this file is from Segment's website: // https://segment.com/docs/sources/website/analytics.js/quickstart/ -import { configuration } from './config'; +import { getConfig } from '@edx/frontend-platform'; (function () { // Create a queue, but don't obliterate an existing one! @@ -81,5 +81,5 @@ import { configuration } from './config'; // Load Analytics.js with your key, which will automatically // load the tools you've enabled for your account. Boosh! - analytics.load(configuration.SEGMENT_KEY); + analytics.load(getConfig().SEGMENT_KEY); }());