Unverified Commit b76a94a8 authored by Kyle Keating's avatar Kyle Keating Committed by GitHub

Call requestGroups to get groups filter

Allow `groups` filter to resolve from an RCP ancestor frame
- If the groups array exists at load time, leave it in place.
- If the groups array value is equal to '$rcp:requestGroups' then fetch that value with a second RCP request to the ancestor frame.
parent b1895465
......@@ -25,6 +25,7 @@ export default function groups(
serviceUrl,
session,
settings,
toastMessenger,
auth
) {
const svc = serviceConfig(settings);
......@@ -87,7 +88,7 @@ export default function groups(
* @param {string|null} directLinkedGroupId
* @return {Group[]}
*/
function filterGroups(
async function filterGroups(
groups,
isLoggedIn,
directLinkedAnnotationGroupId,
......@@ -106,14 +107,21 @@ export default function groups(
directLinkedGroupId = null;
}
}
// If service groups are specified only return those.
// If a service group doesn't exist in the list of groups don't return it.
if (svc && svc.groups) {
const focusedGroups = groups.filter(
g => svc.groups.includes(g.id) || svc.groups.includes(g.groupid)
);
return focusedGroups;
try {
// The groups may not be immediately available if they are being fetched via RPC.
const focusedGroups = await svc.groups;
const filteredGroups = groups.filter(
g => focusedGroups.includes(g.id) || focusedGroups.includes(g.groupid)
);
return filteredGroups;
} catch (e) {
toastMessenger.error(e.message);
// Don't show any groups if we catch an error
return [];
}
}
// Logged-in users always see the "Public" group.
......@@ -295,7 +303,7 @@ export default function groups(
// Step 4. Combine all the groups into a single list and set additional
// metadata on them that will be used elsewhere in the app.
const isLoggedIn = token !== null;
const groups = filterGroups(
const groups = await filterGroups(
combineGroups(myGroups, featuredGroups, documentUri),
isLoggedIn,
directLinkedAnnotationGroupId,
......
......@@ -47,6 +47,7 @@ describe('groups', function () {
let fakeRootScope;
let fakeServiceUrl;
let fakeMetadata;
let fakeToastMessenger;
beforeEach(function () {
fakeAuth = {
......@@ -57,6 +58,10 @@ describe('groups', function () {
isReply: sinon.stub(),
};
fakeToastMessenger = {
error: sinon.stub(),
};
fakeStore = fakeReduxStore(
{
frames: [{ uri: 'http://example.org' }],
......@@ -151,6 +156,7 @@ describe('groups', function () {
fakeServiceUrl,
fakeSession,
fakeSettings,
fakeToastMessenger,
fakeAuth
);
}
......@@ -746,27 +752,40 @@ describe('groups', function () {
services: [{}],
expected: ['__world__', 'abc123', 'def456'],
},
].forEach(({ description, services, expected }) => {
it(description, () => {
fakeSettings.services = services;
const svc = service();
{
description: 'does not show any groups if the groups promise rejects',
services: [
{ groups: Promise.reject(new Error('something went wrong')) },
],
toastMessageError: 'something went wrong',
expected: [],
},
].forEach(
({ description, services, expected, toastMessageError = null }) => {
it(description, () => {
fakeSettings.services = services;
const svc = service();
// Create groups response from server.
const groups = [
{ name: 'Public', id: '__world__' },
{ name: 'ABC', id: 'abc123', groupid: 'group:42@example.com' },
{ name: 'DEF', id: 'def456', groupid: null },
];
// Create groups response from server.
const groups = [
{ name: 'Public', id: '__world__' },
{ name: 'ABC', id: 'abc123', groupid: 'group:42@example.com' },
{ name: 'DEF', id: 'def456', groupid: null },
];
fakeApi.groups.list.returns(Promise.resolve(groups));
fakeApi.profile.groups.read.returns(Promise.resolve([]));
fakeApi.groups.list.returns(Promise.resolve(groups));
fakeApi.profile.groups.read.returns(Promise.resolve([]));
return svc.load().then(groups => {
let displayedGroups = groups.map(g => g.id);
assert.deepEqual(displayedGroups, expected);
return svc.load().then(groups => {
let displayedGroups = groups.map(g => g.id);
assert.deepEqual(displayedGroups, expected);
if (toastMessageError) {
assert.calledWith(fakeToastMessenger.error, toastMessageError);
}
});
});
});
});
}
);
});
describe('#get', function () {
......
......@@ -10,7 +10,7 @@ class FakeWindow {
}
}
describe('sidebar/cross-origin-rcp', function () {
describe('sidebar/cross-origin-rpc', function () {
let fakeStore;
let fakeWarnOnce;
let fakeWindow;
......
......@@ -132,9 +132,43 @@ async function fetchConfigRpc(appConfig, parentFrame, origin) {
[],
3000
);
return fetchConfigEmbed(appConfig, remoteConfig);
// Closure for the RPC call to scope parentFrame and origin variables.
const rpcCall = (method, args = [], timeout = 3000) =>
postMessageJsonRpc.call(parentFrame, origin, method, args, timeout);
const mergedConfig = fetchConfigEmbed(appConfig, remoteConfig);
return fetchGroupsAsync(mergedConfig, rpcCall);
}
/**
* Potentially mutates the `groups` config object(s) to be a promise that
* resolves right away if the `groups` value exists in the original
* config, or returns a promise that resolves with a secondary RPC
* call to `requestGroups` when the `groups` value is '$rpc:requestGroups'
* If the `groups` value is missing or falsely then it won't be mutated.
*
* This allows the app to start with an incomplete config and then lazily
* fill in the `groups` value(s) later when its ready. This helps speed
* up the loading process.
*
* @param {Object} config - The configuration object to mutate. This should
* already have the `services` value
* @param {function} rpcCall - RPC method
* (method, args, timeout) => Promise
* @return {Object} - The mutated settings
*/
async function fetchGroupsAsync(config, rpcCall) {
if (Array.isArray(config.services)) {
config.services.forEach((service, index) => {
if (service.groups === '$rpc:requestGroups') {
// The `groups` need to be fetched from a secondary RPC call
service.groups = rpcCall('requestGroups', [index], 5000).catch(() => {
throw new Error('Unable to fetch groups');
});
}
});
}
return config;
}
/**
* Fetch the host configuration and merge it with the app configuration from h.
*
......
......@@ -178,12 +178,14 @@ describe('sidebar.util.fetch-config', () => {
it('makes an RPC request to `requestConfig` ', async () => {
await fetchConfig({}, fakeWindow);
fakeJsonRpc.call.calledWithExactly(
fakeTopWindow,
'https://embedder.com',
'requestConfig',
[],
3000
assert.isTrue(
fakeJsonRpc.call.calledWithExactly(
fakeTopWindow,
'https://embedder.com',
'requestConfig',
[],
3000
)
);
});
......@@ -219,7 +221,7 @@ describe('sidebar.util.fetch-config', () => {
}
});
it('creates a merged config when the rpc requests returns the host config` ', async () => {
it('creates a merged config when the RPC requests returns the host config', async () => {
const appConfig = { foo: 'bar', appType: 'via' };
fakeJsonRpc.call.resolves({ foo: 'baz' }); // host config
const result = await fetchConfig(appConfig, fakeWindow);
......@@ -237,6 +239,52 @@ describe('sidebar.util.fetch-config', () => {
const result = fetchConfig(appConfig, fakeWindow);
return assertPromiseIsRejected(result, 'Nope');
});
it('returns the `groups` array with the initial host config request', async () => {
const appConfig = {
services: [{ groups: ['group1', 'group2'] }],
appType: 'via',
};
fakeJsonRpc.call.onFirstCall().resolves({ foo: 'baz' }); // host config
const result = await fetchConfig(appConfig, fakeWindow);
assert.deepEqual(result.services[0].groups, ['group1', 'group2']);
});
it("creates a merged config where `groups` is a promise when its initial value is '$rpc:requestGroups'", async () => {
const appConfig = {
services: [{ groups: '$rpc:requestGroups' }],
appType: 'via',
};
fakeJsonRpc.call.onFirstCall().resolves({ foo: 'baz' }); // host config
fakeJsonRpc.call.onSecondCall().resolves(['group1', 'group2']); // requestGroups
const result = await fetchConfig(appConfig, fakeWindow);
assert.deepEqual(await result.services[0].groups, ['group1', 'group2']);
assert.isTrue(
fakeJsonRpc.call.getCall(1).calledWithExactly(
fakeTopWindow,
'https://embedder.com',
'requestGroups',
[0], // passes service index to requestGroups
5000
)
);
});
it('throws an error when the RPC call to `requestGroups` fails', async () => {
const appConfig = {
services: [{ groups: '$rpc:requestGroups' }],
appType: 'via',
};
fakeJsonRpc.call.onFirstCall().resolves({ foo: 'baz' }); // host config
fakeJsonRpc.call.onSecondCall().rejects(); // requestGroups
const result = await fetchConfig(appConfig, fakeWindow);
try {
await result.services[0].groups;
throw new Error('Failed to catch error');
} catch (e) {
assert.equal(e.message, 'Unable to fetch groups');
}
});
});
context('incorrect requestConfigFromFrame object', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment