Skip to content

Commit

Permalink
fix: use getConfig in order to support runtime configuration (#286) (#…
Browse files Browse the repository at this point in the history
…287)

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()`.

Bumps 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.)

Co-authored-by: Ghassan Maslamani <[email protected]>
  • Loading branch information
kdmccormick and ghassanmas authored Nov 28, 2022
1 parent bccd87f commit c4846f9
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 33 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions src/components/GradebookHeader/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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() {
Expand Down
16 changes: 0 additions & 16 deletions src/config/index.js

This file was deleted.

4 changes: 2 additions & 2 deletions src/data/services/lms/urls.js
Original file line number Diff line number Diff line change
@@ -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() || '';

Expand Down
4 changes: 2 additions & 2 deletions src/data/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
14 changes: 7 additions & 7 deletions src/data/store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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: jest.fn(() => ({
SEGMENT_KEY: 'a-fake-segment-key',
},
})),
}));
jest.mock('redux-beacon', () => ({
createMiddleware: jest.fn((map, model) => ({ map, model })),
Expand Down Expand Up @@ -60,17 +60,17 @@ 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.mockImplementation(() => ({ SEGMENT_KEY: false }));
});
it('exports thunk and logger middleware, composed and applied with dev tools', () => {
expect(createStore().middleware).toEqual(
composeWithDevTools(applyMiddleware(thunkMiddleware, createLogger())),
);
});
afterEach(() => {
configuration.SEGMENT_KEY = key;
getConfig.mockImplementation(() => ({ SEGMENT_KEY: key }));
});
});
});
Expand Down
17 changes: 17 additions & 0 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down
17 changes: 16 additions & 1 deletion src/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,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';
Expand All @@ -19,6 +20,7 @@ jest.mock('react-dom', () => ({
jest.mock('@edx/frontend-platform', () => ({
APP_READY: 'app-is-ready-key',
initialize: jest.fn(),
mergeConfig: jest.fn(),
subscribe: jest.fn(),
}));
jest.mock('@edx/frontend-component-header', () => ({
Expand Down Expand Up @@ -46,10 +48,23 @@ describe('app registry', () => {
ReactDOM.render(<App />, 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),
},
});
});
test('initialize config loads LMS_BASE_URL from env', () => {
const oldEnv = process.env;
const initializeArg = initialize.mock.calls[0][0];
process.env = { ...oldEnv, LMS_BASE_URL: 'http://example.com/fake' };
initializeArg.handlers.config();
expect(mergeConfig).toHaveBeenCalledWith(
expect.objectContaining({ LMS_BASE_URL: 'http://example.com/fake' }),
);
process.env = oldEnv;
});
});
4 changes: 2 additions & 2 deletions src/segment.js
Original file line number Diff line number Diff line change
@@ -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!
Expand Down Expand Up @@ -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);
}());

0 comments on commit c4846f9

Please sign in to comment.