fix: use getConfig in order to support runtime configuration (#286)
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 <ghassan.maslamani@gmail.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 };
|
||||
@@ -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() || '';
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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: jest.fn(() => ({
|
||||
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.mockImplementation(() => ({ 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.mockImplementation(() => ({ SEGMENT_KEY: key }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
@@ -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', () => ({
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}());
|
||||
|
||||
Reference in New Issue
Block a user