Compare commits

...

6 Commits

Author SHA1 Message Date
Leangseu Kim
54c6c57b42 feat: upgraded to node v18, added .nvmrc and updated workflows 2023-06-09 09:19:12 +02:00
Adolfo R. Brandes
6722dbdce5 Merge pull request #185 from arbrandes/runtime-config-palm 2023-06-05 16:40:57 +01:00
Adolfo R. Brandes
fed06641df refactor: use getConfig 2023-05-31 12:46:23 -03:00
Adolfo R. Brandes
689b8b48f0 feat: Support runtime configuration
frontend-platform supports runtime configuration since 2.5.0 (see the PR
that introduced it[1], but it requires MFE cooperation.  This implements
just that: by avoiding making configuration values constant, it should
now be possible to change them after initialization.

Almost all changes here relate to the `LMS_BASE_URL` setting, which in
most places was treated as a constant.

[1] openedx/frontend-platform#335
2023-05-31 12:46:23 -03:00
Adolfo R. Brandes
258f4377d8 Merge pull request #181 from raccoongang/palm/fix-location-id 2023-05-15 10:37:48 -03:00
Eugene Dyudyunov
dcdc96778c fix: BadOraLocationResponse error
Refactor the locationId constant for the subdirectory-based
deployments support.

Exclude the MFE's `PUBLIC_PATH` from the constant.

The `window.location.pathname` example:
```
<PUBLIC_PATH>block-v1:oragrading+oragrading+oragrading+type@openassessment+block@ee217e897a954c1faa3b29317da0f2e7
```
Where the `PUBLIC_PATH` could be:
- `'/'` - for subdomain-based deployments (default)
- `'/mfe-specifix-public-path/'` - for subdirectory-based deployments
2023-05-12 15:37:12 +03:00
15 changed files with 60 additions and 76 deletions

View File

@@ -11,17 +11,16 @@ on:
jobs: jobs:
tests: tests:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
strategy:
matrix:
node: [16]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs - name: Setup Nodejs
uses: actions/setup-node@v2 uses: actions/setup-node@v3
# Because of node 18 bug (https://github.com/nodejs/node/issues/47563), Pinning node version 18.15 until the next release of node
with: with:
node-version: ${{ matrix.node }} node-version: 18.15
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
18.15

View File

@@ -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 };

View File

@@ -22,7 +22,7 @@ export const ListViewBreadcrumb = ({ courseId, oraName }) => (
</Hyperlink> </Hyperlink>
<p className="py-4"> <p className="py-4">
<span className="h3">{oraName}</span> <span className="h3">{oraName}</span>
<Hyperlink className="align-middle" destination={urls.ora(courseId, locationId)}> <Hyperlink className="align-middle" destination={urls.ora(courseId, locationId())}>
<Icon src={Launch} className="d-inline-block" /> <Icon src={Launch} className="d-inline-block" />
</Hyperlink> </Hyperlink>
</p> </p>

View File

@@ -50,7 +50,7 @@ describe('ListViewBreadcrumb component', () => {
test('ora destination', () => { test('ora destination', () => {
expect( expect(
el.find(Hyperlink).at(1).props().destination, el.find(Hyperlink).at(1).props().destination,
).toEqual(urls.ora(props.courseId, constants.locationId)); ).toEqual(urls.ora(props.courseId, constants.locationId()));
}); });
}); });
describe('mapStateToProps', () => { describe('mapStateToProps', () => {

View File

@@ -1,4 +1,4 @@
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
export const routePath = `${getConfig().PUBLIC_PATH}:courseId`; export const routePath = () => `${getConfig().PUBLIC_PATH}:courseId`;
export const locationId = window.location.pathname.slice(1); export const locationId = () => window.location.pathname.replace(getConfig().PUBLIC_PATH, '');

View File

@@ -4,7 +4,7 @@ import * as constants from './app';
jest.unmock('./app'); jest.unmock('./app');
jest.mock('@edx/frontend-platform', () => { jest.mock('@edx/frontend-platform', () => {
const PUBLIC_PATH = 'test-public-path'; const PUBLIC_PATH = '/test-public-path/';
return { return {
getConfig: () => ({ PUBLIC_PATH }), getConfig: () => ({ PUBLIC_PATH }),
PUBLIC_PATH, PUBLIC_PATH,
@@ -13,12 +13,12 @@ jest.mock('@edx/frontend-platform', () => {
describe('app constants', () => { describe('app constants', () => {
test('route path draws from public path and adds courseId', () => { test('route path draws from public path and adds courseId', () => {
expect(constants.routePath).toEqual(`${platform.PUBLIC_PATH}:courseId`); expect(constants.routePath()).toEqual(`${platform.PUBLIC_PATH}:courseId`);
}); });
test('locationId returns trimmed pathname', () => { test('locationId returns trimmed pathname', () => {
const old = window.location; const old = window.location;
window.location = { pathName: '/somePath.jpg' }; window.location = { pathName: `${platform.PUBLIC_PATH}somePath.jpg` };
expect(constants.locationId).toEqual(window.location.pathname.slice(1)); expect(constants.locationId()).toEqual(window.location.pathname.replace(platform.PUBLIC_PATH, ''));
window.location = old; window.location = old;
}); });
}); });

View File

@@ -15,7 +15,7 @@ import * as module from './app';
*/ */
export const initialize = () => (dispatch) => { export const initialize = () => (dispatch) => {
dispatch(initializeApp({ dispatch(initializeApp({
locationId, locationId: locationId(),
onSuccess: (response) => { onSuccess: (response) => {
dispatch(actions.app.loadIsEnabled(response.isEnabled)); dispatch(actions.app.loadIsEnabled(response.isEnabled));
dispatch(actions.app.loadOraMetadata(response.oraMetadata)); dispatch(actions.app.loadOraMetadata(response.oraMetadata));

View File

@@ -25,7 +25,7 @@ describe('app thunkActions', () => {
[[dispatchedAction]] = dispatch.mock.calls; [[dispatchedAction]] = dispatch.mock.calls;
}); });
it('dispatches initializeApp with locationId and onSuccess', () => { it('dispatches initializeApp with locationId and onSuccess', () => {
expect(dispatchedAction.initializeApp.locationId).toEqual(locationId); expect(dispatchedAction.initializeApp.locationId).toEqual(locationId());
expect(typeof dispatchedAction.initializeApp.onSuccess).toEqual('function'); expect(typeof dispatchedAction.initializeApp.onSuccess).toEqual('function');
}); });
describe('on success', () => { describe('on success', () => {

View File

@@ -47,7 +47,7 @@ export const zipFiles = async (files, blobs, username) => {
} }
const zipFile = await zipWriter.close(); const zipFile = await zipWriter.close();
const zipName = `${username}-${locationId}.zip`; const zipName = `${username}-${locationId()}.zip`;
FileSaver.saveAs(zipFile, zipName); FileSaver.saveAs(zipFile, zipName);
}; };

View File

@@ -32,8 +32,8 @@ import {
* } * }
*/ */
const initializeApp = () => get( const initializeApp = () => get(
stringifyUrl(urls.oraInitializeUrl, { stringifyUrl(urls.oraInitializeUrl(), {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
}), }),
).then(response => response.data); ).then(response => response.data);
@@ -48,8 +48,8 @@ const initializeApp = () => get(
* } * }
*/ */
const fetchSubmission = (submissionUUID) => get( const fetchSubmission = (submissionUUID) => get(
stringifyUrl(urls.fetchSubmissionUrl, { stringifyUrl(urls.fetchSubmissionUrl(), {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}), }),
).then(response => response.data); ).then(response => response.data);
@@ -61,8 +61,8 @@ const fetchSubmission = (submissionUUID) => get(
* } * }
*/ */
const fetchSubmissionFiles = (submissionUUID) => get( const fetchSubmissionFiles = (submissionUUID) => get(
stringifyUrl(urls.fetchSubmissionFilesUrl, { stringifyUrl(urls.fetchSubmissionFilesUrl(), {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}), }),
).then(response => response.data); ).then(response => response.data);
@@ -78,8 +78,8 @@ const fetchSubmissionFiles = (submissionUUID) => get(
* } * }
*/ */
const fetchSubmissionStatus = (submissionUUID) => get( const fetchSubmissionStatus = (submissionUUID) => get(
stringifyUrl(urls.fetchSubmissionStatusUrl, { stringifyUrl(urls.fetchSubmissionStatusUrl(), {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}), }),
).then(response => response.data); ).then(response => response.data);
@@ -89,8 +89,8 @@ const fetchSubmissionStatus = (submissionUUID) => get(
* @param {string} submissionUUID * @param {string} submissionUUID
*/ */
const lockSubmission = (submissionUUID) => post( const lockSubmission = (submissionUUID) => post(
stringifyUrl(urls.fetchSubmissionLockUrl, { stringifyUrl(urls.fetchSubmissionLockUrl(), {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}), }),
).then(response => response.data); ).then(response => response.data);
@@ -100,8 +100,8 @@ const lockSubmission = (submissionUUID) => post(
* @param {string} submissionUUID * @param {string} submissionUUID
*/ */
const unlockSubmission = (submissionUUID) => client().delete( const unlockSubmission = (submissionUUID) => client().delete(
stringifyUrl(urls.fetchSubmissionLockUrl, { stringifyUrl(urls.fetchSubmissionLockUrl(), {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}), }),
).then(response => response.data); ).then(response => response.data);
@@ -112,8 +112,8 @@ const unlockSubmission = (submissionUUID) => client().delete(
*/ */
const batchUnlockSubmissions = (submissionUUIDs) => post( const batchUnlockSubmissions = (submissionUUIDs) => post(
stringifyUrl( stringifyUrl(
urls.batchUnlockSubmissionsUrl, urls.batchUnlockSubmissionsUrl(),
{ [paramKeys.oraLocation]: locationId }, { [paramKeys.oraLocation]: locationId() },
), ),
{ submissionUUIDs }, { submissionUUIDs },
).then(response => response.data); ).then(response => response.data);
@@ -123,8 +123,8 @@ const batchUnlockSubmissions = (submissionUUIDs) => post(
* @param {object} gradeData - full grading submission data * @param {object} gradeData - full grading submission data
*/ */
const updateGrade = (submissionUUID, gradeData) => post( const updateGrade = (submissionUUID, gradeData) => post(
stringifyUrl(urls.updateSubmissionGradeUrl, { stringifyUrl(urls.updateSubmissionGradeUrl(), {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}), }),
gradeData, gradeData,

View File

@@ -13,7 +13,7 @@ jest.mock('./utils', () => ({
})); }));
jest.mock('data/constants/app', () => ({ jest.mock('data/constants/app', () => ({
locationId: 'test-location-id', locationId: () => 'test-location-id',
})); }));
const gradeData = 'test-grade-data'; const gradeData = 'test-grade-data';
@@ -37,10 +37,10 @@ const testAPI = ({
...otherExpected ...otherExpected
}, },
}) => { }) => {
it(`returns ${method}(${urlKey}) with correct args and reoslves with response data`, () => ( it(`returns ${method}(${urlKey}) with correct args and resolves with response data`, () => (
promise.then((data) => { promise.then((data) => {
expect(data[method]).toEqual({ expect(data[method]).toEqual({
url: stringifyUrl(urls[urlKey], urlParams), url: stringifyUrl(urls[urlKey](), urlParams),
...otherExpected, ...otherExpected,
}); });
}) })
@@ -54,7 +54,7 @@ describe('lms service api methods', () => {
method: methodKeys.get, method: methodKeys.get,
expected: { expected: {
urlKey: urlKeys.oraInitializeUrl, urlKey: urlKeys.oraInitializeUrl,
urlParams: { [paramKeys.oraLocation]: locationId }, urlParams: { [paramKeys.oraLocation]: locationId() },
}, },
}); });
}); });
@@ -65,7 +65,7 @@ describe('lms service api methods', () => {
expected: { expected: {
urlKey: urlKeys.fetchSubmissionUrl, urlKey: urlKeys.fetchSubmissionUrl,
urlParams: { urlParams: {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}, },
}, },
@@ -78,7 +78,7 @@ describe('lms service api methods', () => {
expected: { expected: {
urlKey: urlKeys.fetchSubmissionFilesUrl, urlKey: urlKeys.fetchSubmissionFilesUrl,
urlParams: { urlParams: {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}, },
}, },
@@ -91,7 +91,7 @@ describe('lms service api methods', () => {
expected: { expected: {
urlKey: urlKeys.fetchSubmissionStatusUrl, urlKey: urlKeys.fetchSubmissionStatusUrl,
urlParams: { urlParams: {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}, },
}, },
@@ -104,7 +104,7 @@ describe('lms service api methods', () => {
expected: { expected: {
urlKey: urlKeys.fetchSubmissionLockUrl, urlKey: urlKeys.fetchSubmissionLockUrl,
urlParams: { urlParams: {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}, },
}, },
@@ -117,7 +117,7 @@ describe('lms service api methods', () => {
expected: { expected: {
urlKey: urlKeys.fetchSubmissionLockUrl, urlKey: urlKeys.fetchSubmissionLockUrl,
urlParams: { urlParams: {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}, },
}, },
@@ -130,7 +130,7 @@ describe('lms service api methods', () => {
expected: { expected: {
urlKey: urlKeys.batchUnlockSubmissionsUrl, urlKey: urlKeys.batchUnlockSubmissionsUrl,
urlParams: { urlParams: {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
}, },
data: { submissionUUIDs }, data: { submissionUUIDs },
}, },
@@ -143,7 +143,7 @@ describe('lms service api methods', () => {
expected: { expected: {
urlKey: urlKeys.updateSubmissionGradeUrl, urlKey: urlKeys.updateSubmissionGradeUrl,
urlParams: { urlParams: {
[paramKeys.oraLocation]: locationId, [paramKeys.oraLocation]: locationId(),
[paramKeys.submissionUUID]: submissionUUID, [paramKeys.submissionUUID]: submissionUUID,
}, },
data: gradeData, data: gradeData,

View File

@@ -1,20 +1,20 @@
import { StrictDict } from 'utils'; import { StrictDict } from 'utils';
import { configuration } from 'config'; import { getConfig } from '@edx/frontend-platform';
const baseUrl = `${configuration.LMS_BASE_URL}`; const baseUrl = () => getConfig().LMS_BASE_URL;
const api = `${baseUrl}/api/`; const api = () => `${baseUrl()}/api/`;
const baseEsgUrl = `${api}ora_staff_grader/`; const baseEsgUrl = () => `${api()}ora_staff_grader/`;
const oraInitializeUrl = `${baseEsgUrl}initialize`; const oraInitializeUrl = () => `${baseEsgUrl()}initialize`;
const fetchSubmissionUrl = `${baseEsgUrl}submission`; const fetchSubmissionUrl = () => `${baseEsgUrl()}submission`;
const fetchSubmissionFilesUrl = `${baseEsgUrl}submission/files`; const fetchSubmissionFilesUrl = () => `${baseEsgUrl()}submission/files`;
const fetchSubmissionStatusUrl = `${baseEsgUrl}submission/status`; const fetchSubmissionStatusUrl = () => `${baseEsgUrl()}submission/status`;
const fetchSubmissionLockUrl = `${baseEsgUrl}submission/lock`; const fetchSubmissionLockUrl = () => `${baseEsgUrl()}submission/lock`;
const batchUnlockSubmissionsUrl = `${baseEsgUrl}submission/batch/unlock`; const batchUnlockSubmissionsUrl = () => `${baseEsgUrl()}submission/batch/unlock`;
const updateSubmissionGradeUrl = `${baseEsgUrl}submission/grade`; const updateSubmissionGradeUrl = () => `${baseEsgUrl()}submission/grade`;
const course = (courseId) => `${baseUrl}/courses/${courseId}`; const course = (courseId) => `${baseUrl()}/courses/${courseId}`;
const openResponse = (courseId) => ( const openResponse = (courseId) => (
`${course(courseId)}/instructor#view-open_response_assessment` `${course(courseId)}/instructor#view-open_response_assessment`

View File

@@ -1,6 +1,6 @@
// The code in this file is from Segment's website: // The code in this file is from Segment's website:
// https://segment.com/docs/sources/website/analytics.js/quickstart/ // https://segment.com/docs/sources/website/analytics.js/quickstart/
import { configuration } from './config'; import { getConfig } from '@edx/frontend-platform';
(function () { (function () {
// Create a queue, but don't obliterate an existing one! // 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 Analytics.js with your key, which will automatically
// load the tools you've enabled for your account. Boosh! // load the tools you've enabled for your account. Boosh!
analytics.load(configuration.SEGMENT_KEY); analytics.load(getConfig().SEGMENT_KEY);
}()); }());

View File

@@ -113,7 +113,7 @@ jest.mock('@edx/paragon/icons', () => ({
})); }));
jest.mock('data/constants/app', () => ({ jest.mock('data/constants/app', () => ({
locationId: 'fake-location-id', locationId: () => 'fake-location-id',
})); }));
jest.mock('hooks', () => ({ jest.mock('hooks', () => ({