Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ const ExampleComponent = () => {
};
```

#### Overriding default external links

A `getExternalLinkUrl` function is provided in `config.js` which can be used to override default external links. To make use of this function, provide an object that maps default links to custom links. This object should be added to the `config` object defined in the `env.config.[js,jsx,ts,tsx]`, and must be named `externalLinkUrlOverrides`. Here is an example:

```js
// env.config.js

const config = {
// other custom configuration here
externalLinkUrlOverrides: {
"https://docs.openedx.org/en/latest/educators/index.html": "https://custom.example.com/educators/index.html",
"https://creativecommons.org/licenses": "https://www.tldrlegal.com/license/creative-commons-attribution-cc",
},
};

export default config;
```

### Service interfaces

Each service (analytics, auth, i18n, logging) provided by frontend-platform has an API contract which all implementations of that service are guaranteed to fulfill. Applications that use frontend-platform can use its configured services via a convenient set of exported functions. An application that wants to use the service interfaces need only initialize them via the initialize() function, optionally providing custom service interfaces as desired (you probably won't need to).
Expand Down
3 changes: 3 additions & 0 deletions env.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// Also note that in an actual application this file would be added to .gitignore.
const config = {
JS_FILE_VAR: 'JS_FILE_VAR_VALUE_FOR_EXAMPLE_APP',
externalLinkUrlOverrides: {
"https://github.com/openedx/docs.openedx.org/": "https://docs.openedx.org/",
}
};

export default config;
6 changes: 5 additions & 1 deletion example/ExamplePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Link } from 'react-router-dom';
import { injectIntl, useIntl } from '@edx/frontend-platform/i18n';
import { logInfo } from '@edx/frontend-platform/logging';
import { AppContext } from '@edx/frontend-platform/react';
import { ensureConfig, mergeConfig, getConfig } from '@edx/frontend-platform';
import {
ensureConfig, mergeConfig, getConfig, getExternalLinkUrl,
} from '@edx/frontend-platform';
/* eslint-enable import/no-extraneous-dependencies */
import messages from './messages';

Expand Down Expand Up @@ -49,6 +51,8 @@ function ExamplePage() {
<AuthenticatedUser />
<p>EXAMPLE_VAR env var came through: <strong>{getConfig().EXAMPLE_VAR}</strong></p>
<p>JS_FILE_VAR var came through: <strong>{getConfig().JS_FILE_VAR}</strong></p>
<p>External link to <a href={getExternalLinkUrl('https://github.com/openedx/docs.openedx.org/')}>Open edX docs</a> (customized link).</p>
<p>External link to <a href={getExternalLinkUrl('https://open-edx-proposals.readthedocs.io/en/latest/')}>Open edX OEPs</a> (non-customized link).</p>
<p>Visit <Link to="/authenticated">authenticated page</Link>.</p>
<p>Visit <Link to="/error_example">error page</Link>.</p>
</div>
Expand Down
28 changes: 28 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,34 @@ export function ensureConfig(keys, requester = 'unspecified application code') {
});
}

/**
* Get an external link URL based on the URL provided. If the passed in URL is overridden in the
* `externalLinkUrlOverrides` object, it will return the overridden URL. Otherwise, it will return
* the provided URL.
*
*
* @param {string} url - The default URL.
* @returns {string} - The external link URL. Defaults to the input URL if not found in the
* `externalLinkUrlOverrides` object. If the input URL is invalid, '#' is returned.
*
* @example
* import { getExternalLinkUrl } from '@edx/frontend-platform';
*
* <Hyperlink
* destination={getExternalLinkUrl(data.helpLink)}
* target="_blank"
* >
*/
export function getExternalLinkUrl(url) {
// Guard against non-strings or whitespace-only strings
if (typeof url !== 'string' || !url.trim()) {
return '#';
}

const overriddenLinkUrls = getConfig().externalLinkUrlOverrides || {};
return overriddenLinkUrls[url] || url;
}

/**
* An object describing the current application configuration.
*
Expand Down
76 changes: 76 additions & 0 deletions src/getExternalLinkUrl.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { getExternalLinkUrl, setConfig } from './config';

describe('getExternalLinkUrl', () => {
afterEach(() => {
// Reset config after each test to avoid cross-test pollution
setConfig({});
});

it('should return the url passed in when externalLinkUrlOverrides is not set', () => {
setConfig({});
const url = 'https://foo.example.com';
expect(getExternalLinkUrl(url)).toBe(url);
});

it('should return the url passed in when externalLinkUrlOverrides does not have the url mapping', () => {
setConfig({
externalLinkUrlOverrides: {
'https://bar.example.com': 'https://mapped.example.com',
},
});
const url = 'https://foo.example.com';
expect(getExternalLinkUrl(url)).toBe(url);
});

it('should return the mapped url when externalLinkUrlOverrides has the url mapping', () => {
const url = 'https://foo.example.com';
const mappedUrl = 'https://mapped.example.com';
setConfig({ externalLinkUrlOverrides: { [url]: mappedUrl } });
expect(getExternalLinkUrl(url)).toBe(mappedUrl);
});

it('should handle empty externalLinkUrlOverrides object', () => {
setConfig({ externalLinkUrlOverrides: {} });
const url = 'https://foo.example.com';
expect(getExternalLinkUrl(url)).toBe(url);
});

it('should guard against empty string argument', () => {
const fallbackResult = '#';
setConfig({ externalLinkUrlOverrides: { foo: 'bar' } });
expect(getExternalLinkUrl(undefined)).toBe(fallbackResult);
});

it('should guard against non-string argument', () => {
const fallbackResult = '#';
setConfig({ externalLinkUrlOverrides: { foo: 'bar' } });
expect(getExternalLinkUrl(null)).toBe(fallbackResult);
expect(getExternalLinkUrl(42)).toBe(fallbackResult);
});

it('should not throw if externalLinkUrlOverrides is not an object', () => {
setConfig({ externalLinkUrlOverrides: null });
const url = 'https://foo.example.com';
expect(getExternalLinkUrl(url)).toBe(url);
setConfig({ externalLinkUrlOverrides: 42 });
expect(getExternalLinkUrl(url)).toBe(url);
});

it('should work with multiple mappings', () => {
setConfig({
externalLinkUrlOverrides: {
'https://a.example.com': 'https://mapped-a.example.com',
'https://b.example.com': 'https://mapped-b.example.com',
},
});
expect(getExternalLinkUrl('https://a.example.com')).toBe(
'https://mapped-a.example.com',
);
expect(getExternalLinkUrl('https://b.example.com')).toBe(
'https://mapped-b.example.com',
);
expect(getExternalLinkUrl('https://c.example.com')).toBe(
'https://c.example.com',
);
});
});
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export {
setConfig,
mergeConfig,
ensureConfig,
getExternalLinkUrl,
} from './config';
export {
initializeMockApp,
Expand Down