Unverified Commit 6e6f3b89 authored by Hannah Stepanek's avatar Hannah Stepanek Committed by GitHub

Merge pull request #1052 from hypothesis/remove-feature-flag-from-group-service

Remove feature flag from group service
parents 87c31b3c 896ceaab
...@@ -4,7 +4,11 @@ ...@@ -4,7 +4,11 @@
function GroupListSectionController() { function GroupListSectionController() {
this.isSelectable = function(groupId) { this.isSelectable = function(groupId) {
const group = this.sectionGroups.find(g => g.id === groupId); const group = this.sectionGroups.find(g => g.id === groupId);
return !this.disableOosGroupSelection || group.isScopedToUri; return (
!this.disableOosGroupSelection ||
!group.scopes.enforced ||
group.isScopedToUri
);
}; };
} }
......
...@@ -31,26 +31,49 @@ describe('groupListSection', () => { ...@@ -31,26 +31,49 @@ describe('groupListSection', () => {
{ {
description: 'always returns true if disableOosGroupSelection is false', description: 'always returns true if disableOosGroupSelection is false',
fakeDisableOosGroupSelection: false, fakeDisableOosGroupSelection: false,
scopesEnforced: true,
expectedIsSelectable: [true, true], expectedIsSelectable: [true, true],
}, },
{ {
description: description:
'always returns true if disableOosGroupSelection is undefined', 'always returns true if disableOosGroupSelection is undefined',
fakeDisableOosGroupSelection: undefined, fakeDisableOosGroupSelection: undefined,
scopesEnforced: true,
expectedIsSelectable: [true, true], expectedIsSelectable: [true, true],
}, },
{ {
description: description:
'returns false if disableOosGroupSelection is true and group is out of scope', 'returns false if disableOosGroupSelection is true and group is out of scope',
fakeDisableOosGroupSelection: true, fakeDisableOosGroupSelection: true,
scopesEnforced: true,
expectedIsSelectable: [true, false], expectedIsSelectable: [true, false],
}, },
{
description:
'returns true if disableOosGroupSelection is true and group is out of scope but not enforced',
fakeDisableOosGroupSelection: true,
scopesEnforced: false,
expectedIsSelectable: [true, true],
},
].forEach( ].forEach(
({ description, fakeDisableOosGroupSelection, expectedIsSelectable }) => { ({
description,
fakeDisableOosGroupSelection,
scopesEnforced,
expectedIsSelectable,
}) => {
it(description, () => { it(description, () => {
const fakeSectionGroups = [ const fakeSectionGroups = [
{ isScopedToUri: true, id: 0 }, {
{ isScopedToUri: false, id: 1 }, isScopedToUri: true,
scopes: { enforced: scopesEnforced },
id: 0,
},
{
isScopedToUri: false,
scopes: { enforced: scopesEnforced },
id: 1,
},
]; ];
const element = createGroupListSection( const element = createGroupListSection(
......
...@@ -16,6 +16,7 @@ const DEFAULT_ORGANIZATION = { ...@@ -16,6 +16,7 @@ const DEFAULT_ORGANIZATION = {
const events = require('../events'); const events = require('../events');
const { awaitStateChange } = require('../util/state-util'); const { awaitStateChange } = require('../util/state-util');
const { combineGroups } = require('../util/groups'); const { combineGroups } = require('../util/groups');
const memoize = require('../util/memoize');
const serviceConfig = require('../service-config'); const serviceConfig = require('../service-config');
// @ngInject // @ngInject
...@@ -77,7 +78,9 @@ function groups( ...@@ -77,7 +78,9 @@ function groups(
// If the main document URL has no groups associated with it, always show // If the main document URL has no groups associated with it, always show
// the "Public" group. // the "Public" group.
const pageHasAssociatedGroups = groups.some(g => g.id !== '__world__'); const pageHasAssociatedGroups = groups.some(
g => g.id !== '__world__' && g.isScopedToUri
);
if (!pageHasAssociatedGroups) { if (!pageHasAssociatedGroups) {
return Promise.resolve(groups); return Promise.resolve(groups);
} }
...@@ -144,7 +147,7 @@ function groups( ...@@ -144,7 +147,7 @@ function groups(
return uri return uri
.then(uri => { .then(uri => {
const params = { const params = {
expand: 'organization', expand: ['organization', 'scopes'],
}; };
if (authority) { if (authority) {
params.authority = authority; params.authority = authority;
...@@ -154,13 +157,11 @@ function groups( ...@@ -154,13 +157,11 @@ function groups(
} }
documentUri = uri; documentUri = uri;
if (features.flagEnabled('community_groups')) { const profileGroupsApi = api.profile.groups.read({
params.expand = ['organization', 'scopes']; expand: params.expand,
const profileParams = { });
expand: ['organization', 'scopes'],
};
const profileGroupsApi = api.profile.groups.read(profileParams);
const listGroupsApi = api.groups.list(params); const listGroupsApi = api.groups.list(params);
return Promise.all([ return Promise.all([
profileGroupsApi, profileGroupsApi,
listGroupsApi, listGroupsApi,
...@@ -169,12 +170,6 @@ function groups( ...@@ -169,12 +170,6 @@ function groups(
combineGroups(myGroups, featuredGroups, documentUri), combineGroups(myGroups, featuredGroups, documentUri),
token, token,
]); ]);
} else {
// Fetch groups from the API.
return api.groups
.list(params, null, { includeMetadata: true })
.then(({ data, token }) => [data, token]);
}
}) })
.then(([groups, token]) => { .then(([groups, token]) => {
const isLoggedIn = token !== null; const isLoggedIn = token !== null;
...@@ -196,9 +191,26 @@ function groups( ...@@ -196,9 +191,26 @@ function groups(
}); });
} }
const sortGroups = memoize(groups => {
// Sort in the following order: scoped, public, private.
// This is for maintaining the order of the old groups menu so when
// the old groups menu is removed this can be removed.
const worldGroups = groups.filter(g => g.id === '__world__');
const nonWorldScopedGroups = groups.filter(
g => g.id !== '__world__' && ['open', 'restricted'].includes(g.type)
);
const remainingGroups = groups.filter(
g => !worldGroups.includes(g) && !nonWorldScopedGroups.includes(g)
);
return nonWorldScopedGroups.concat(worldGroups).concat(remainingGroups);
});
function all() { function all() {
if (features.flagEnabled('community_groups')) {
return store.allGroups(); return store.allGroups();
} }
return sortGroups(store.getInScopeGroups());
}
// Return the full object for the group with the given id. // Return the full object for the group with the given id.
function get(id) { function get(id) {
......
...@@ -70,6 +70,9 @@ describe('groups', function() { ...@@ -70,6 +70,9 @@ describe('groups', function() {
allGroups() { allGroups() {
return this.getState().groups; return this.getState().groups;
}, },
getInScopeGroups() {
return this.getState().groups;
},
focusedGroup() { focusedGroup() {
return this.getState().focusedGroup; return this.getState().focusedGroup;
}, },
...@@ -114,12 +117,7 @@ describe('groups', function() { ...@@ -114,12 +117,7 @@ describe('groups', function() {
}, },
}, },
groups: { groups: {
list: sandbox.stub().returns( list: sandbox.stub().returns(dummyGroups),
Promise.resolve({
data: dummyGroups,
token: '1234',
})
),
}, },
profile: { profile: {
groups: { groups: {
...@@ -151,15 +149,40 @@ describe('groups', function() { ...@@ -151,15 +149,40 @@ describe('groups', function() {
} }
describe('#all', function() { describe('#all', function() {
it('returns all groups', function() { it('returns all groups from store.allGroups when community-groups feature flag is enabled', () => {
const svc = service(); const svc = service();
fakeStore.setState({ groups: dummyGroups }); fakeStore.allGroups = sinon.stub().returns(dummyGroups);
fakeFeatures.flagEnabled.withArgs('community_groups').returns(true);
assert.deepEqual(svc.all(), dummyGroups); assert.deepEqual(svc.all(), dummyGroups);
assert.called(fakeStore.allGroups);
});
it('returns all groups from store.getInScopeGroups when community-groups feature flag is disabled', () => {
const svc = service();
fakeStore.getInScopeGroups = sinon.stub().returns(dummyGroups);
assert.deepEqual(svc.all(), dummyGroups);
assert.called(fakeStore.getInScopeGroups);
});
[[0, 1, 2, 3], [2, 0, 1, 3], [0, 3, 1, 2]].forEach(groupInputOrder => {
it('sorts the groups in the following order: scoped, public, private maintaining order within each category.', () => {
const groups = [
{ id: 0, type: 'open' },
{ id: 1, type: 'restricted' },
{ id: '__world__', type: 'open' },
{ id: 3, type: 'private' },
];
const svc = service();
fakeStore.getInScopeGroups = sinon
.stub()
.returns(groupInputOrder.map(id => groups[id]));
assert.deepEqual(svc.all(), groups);
});
}); });
}); });
describe('#load', function() { describe('#load', function() {
it('combines groups from both endpoints if community-groups feature flag is set', function() { it('combines groups from both endpoints', function() {
const svc = service(); const svc = service();
const groups = [ const groups = [
...@@ -169,7 +192,6 @@ describe('groups', function() { ...@@ -169,7 +192,6 @@ describe('groups', function() {
fakeApi.profile.groups.read.returns(Promise.resolve(groups)); fakeApi.profile.groups.read.returns(Promise.resolve(groups));
fakeApi.groups.list.returns(Promise.resolve([groups[0]])); fakeApi.groups.list.returns(Promise.resolve([groups[0]]));
fakeFeatures.flagEnabled.withArgs('community_groups').returns(true);
return svc.load().then(() => { return svc.load().then(() => {
assert.calledWith(fakeStore.loadGroups, groups); assert.calledWith(fakeStore.loadGroups, groups);
...@@ -184,21 +206,11 @@ describe('groups', function() { ...@@ -184,21 +206,11 @@ describe('groups', function() {
}); });
}); });
it('always sends the `expand` parameter', function() { it('sends `expand` parameter', function() {
const svc = service();
return svc.load().then(() => {
const call = fakeApi.groups.list.getCall(0);
assert.isObject(call.args[0]);
assert.equal(call.args[0].expand, 'organization');
});
});
it('sends `expand` parameter when community-groups feature flag is enabled', function() {
const svc = service(); const svc = service();
fakeApi.groups.list.returns( fakeApi.groups.list.returns(
Promise.resolve([{ id: 'groupa', name: 'GroupA' }]) Promise.resolve([{ id: 'groupa', name: 'GroupA' }])
); );
fakeFeatures.flagEnabled.withArgs('community_groups').returns(true);
return svc.load().then(() => { return svc.load().then(() => {
assert.calledWith( assert.calledWith(
...@@ -241,7 +253,7 @@ describe('groups', function() { ...@@ -241,7 +253,7 @@ describe('groups', function() {
return loaded.then(() => { return loaded.then(() => {
assert.calledWith(fakeApi.groups.list, { assert.calledWith(fakeApi.groups.list, {
document_uri: 'https://asite.com', document_uri: 'https://asite.com',
expand: 'organization', expand: ['organization', 'scopes'],
}); });
}); });
}); });
...@@ -257,7 +269,7 @@ describe('groups', function() { ...@@ -257,7 +269,7 @@ describe('groups', function() {
const svc = service(); const svc = service();
return svc.load().then(() => { return svc.load().then(() => {
assert.calledWith(fakeApi.groups.list, { assert.calledWith(fakeApi.groups.list, {
expand: 'organization', expand: ['organization', 'scopes'],
}); });
}); });
}); });
...@@ -277,12 +289,7 @@ describe('groups', function() { ...@@ -277,12 +289,7 @@ describe('groups', function() {
it('injects a defalt organization if group is missing an organization', function() { it('injects a defalt organization if group is missing an organization', function() {
const svc = service(); const svc = service();
const groups = [{ id: '39r39f', name: 'Ding Dong!' }]; const groups = [{ id: '39r39f', name: 'Ding Dong!' }];
fakeApi.groups.list.returns( fakeApi.groups.list.returns(Promise.resolve(groups));
Promise.resolve({
token: '1234',
data: groups,
})
);
return svc.load().then(groups => { return svc.load().then(groups => {
assert.isObject(groups[0].organization); assert.isObject(groups[0].organization);
assert.hasAllKeys(groups[0].organization, ['id', 'logo']); assert.hasAllKeys(groups[0].organization, ['id', 'logo']);
...@@ -317,12 +324,9 @@ describe('groups', function() { ...@@ -317,12 +324,9 @@ describe('groups', function() {
groups.push({ name: 'BioPub', id: 'biopub' }); groups.push({ name: 'BioPub', id: 'biopub' });
} }
fakeApi.groups.list.returns( fakeAuth.tokenGetter.returns(loggedIn ? '1234' : null);
Promise.resolve({ fakeApi.groups.list.returns(Promise.resolve(groups));
token: loggedIn ? '1234' : null, fakeApi.profile.groups.read.returns(Promise.resolve([]));
data: groups,
})
);
return svc.load().then(groups => { return svc.load().then(groups => {
const publicGroupShown = groups.some(g => g.id === '__world__'); const publicGroupShown = groups.some(g => g.id === '__world__');
...@@ -375,12 +379,8 @@ describe('groups', function() { ...@@ -375,12 +379,8 @@ describe('groups', function() {
{ name: 'DEF', id: 'def456', groupid: null }, { name: 'DEF', id: 'def456', groupid: null },
]; ];
fakeApi.groups.list.returns( fakeApi.groups.list.returns(Promise.resolve(groups));
Promise.resolve({ fakeApi.profile.groups.read.returns(Promise.resolve([]));
token: '1234',
data: groups,
})
);
return svc.load().then(groups => { return svc.load().then(groups => {
let displayedGroups = groups.map(g => g.id); let displayedGroups = groups.map(g => g.id);
......
...@@ -158,6 +158,17 @@ const getCurrentlyViewingGroups = memoize(state => { ...@@ -158,6 +158,17 @@ const getCurrentlyViewingGroups = memoize(state => {
); );
}); });
/**
* Return groups that are scoped to the uri. This is used to return the groups
* that show up in the old groups menu. This should be removed once the new groups
* menu is permanent.
*
* @return {Group[]}
*/
const getInScopeGroups = memoize(state => {
return state.groups.filter(g => g.isScopedToUri);
});
module.exports = { module.exports = {
init, init,
update, update,
...@@ -171,6 +182,7 @@ module.exports = { ...@@ -171,6 +182,7 @@ module.exports = {
getCurrentlyViewingGroups, getCurrentlyViewingGroups,
getFeaturedGroups, getFeaturedGroups,
getMyGroups, getMyGroups,
getInScopeGroups,
focusedGroup, focusedGroup,
focusedGroupId, focusedGroupId,
}, },
......
...@@ -134,6 +134,13 @@ describe('sidebar.store.modules.groups', () => { ...@@ -134,6 +134,13 @@ describe('sidebar.store.modules.groups', () => {
}); });
}); });
describe('getInScopeGroups', () => {
it('returns all groups that are in scope', () => {
store.loadGroups([publicGroup, privateGroup, restrictedOutOfScopeGroup]);
assert.deepEqual(store.getInScopeGroups(), [publicGroup, privateGroup]);
});
});
describe('getGroup', () => { describe('getGroup', () => {
it('returns the group with the given ID', () => { it('returns the group with the given ID', () => {
store.loadGroups([publicGroup, privateGroup]); store.loadGroups([publicGroup, privateGroup]);
......
...@@ -35,10 +35,9 @@ function combineGroups(userGroups, featuredGroups, uri) { ...@@ -35,10 +35,9 @@ function combineGroups(userGroups, featuredGroups, uri) {
} }
function isScopedToUri(group, uri) { function isScopedToUri(group, uri) {
// If the group has scope info, the scoping is enforced, // Groups with no scopes or groups with no uri_patterns defined
// and the uri patterns don't include this page's uri // are considered in scope.
// the group is not selectable, otherwise it is. if (group.scopes && group.scopes.uri_patterns.length > 0) {
if (group.scopes && group.scopes.enforced) {
return uriMatchesScopes(uri, group.scopes.uri_patterns); return uriMatchesScopes(uri, group.scopes.uri_patterns);
} }
return true; return true;
......
...@@ -102,14 +102,14 @@ describe('sidebar.util.groups', () => { ...@@ -102,14 +102,14 @@ describe('sidebar.util.groups', () => {
{ {
description: 'sets `isScopedToUri` to true if `scopes` is missing', description: 'sets `isScopedToUri` to true if `scopes` is missing',
scopes: undefined, scopes: undefined,
shouldBeSelectable: true, isScopedToUri: true,
uri: 'https://foo.com/bar', uri: 'https://foo.com/bar',
}, },
{ {
description: description:
'sets `isScopedToUri` to true if `scopes.enforced` is false', 'sets `isScopedToUri` to true if `scopes.uri_patterns` is empty',
scopes: { enforced: false }, scopes: { uri_patterns: [] },
shouldBeSelectable: true, isScopedToUri: true,
uri: 'https://foo.com/bar', uri: 'https://foo.com/bar',
}, },
{ {
...@@ -119,20 +119,20 @@ describe('sidebar.util.groups', () => { ...@@ -119,20 +119,20 @@ describe('sidebar.util.groups', () => {
enforced: true, enforced: true,
uri_patterns: ['http://foo.com*', 'https://foo.com*'], uri_patterns: ['http://foo.com*', 'https://foo.com*'],
}, },
shouldBeSelectable: true, isScopedToUri: true,
uri: 'https://foo.com/bar', uri: 'https://foo.com/bar',
}, },
{ {
description: description:
'sets `isScopedToUri` to false if `scopes.uri_patterns` do not match the uri', 'sets `isScopedToUri` to false if `scopes.uri_patterns` do not match the uri',
scopes: { enforced: true, uri_patterns: ['http://foo.com*'] }, scopes: { enforced: true, uri_patterns: ['http://foo.com*'] },
shouldBeSelectable: false, isScopedToUri: false,
uri: 'https://foo.com/bar', uri: 'https://foo.com/bar',
}, },
{ {
description: 'it permits multiple *s in the scopes uri pattern', description: 'it permits multiple *s in the scopes uri pattern',
scopes: { enforced: true, uri_patterns: ['https://foo.com*bar*'] }, scopes: { enforced: true, uri_patterns: ['https://foo.com*bar*'] },
shouldBeSelectable: true, isScopedToUri: true,
uri: 'https://foo.com/boo/bar/baz', uri: 'https://foo.com/boo/bar/baz',
}, },
{ {
...@@ -141,17 +141,17 @@ describe('sidebar.util.groups', () => { ...@@ -141,17 +141,17 @@ describe('sidebar.util.groups', () => {
enforced: true, enforced: true,
uri_patterns: ['https://foo.com?bar=foo$[^]($){mu}+&boo=*'], uri_patterns: ['https://foo.com?bar=foo$[^]($){mu}+&boo=*'],
}, },
shouldBeSelectable: true, isScopedToUri: true,
uri: 'https://foo.com?bar=foo$[^]($){mu}+&boo=foo', uri: 'https://foo.com?bar=foo$[^]($){mu}+&boo=foo',
}, },
].forEach(({ description, scopes, shouldBeSelectable, uri }) => { ].forEach(({ description, scopes, isScopedToUri, uri }) => {
it(description, () => { it(description, () => {
const userGroups = [{ id: 'groupa', name: 'GroupA', scopes: scopes }]; const userGroups = [{ id: 'groupa', name: 'GroupA', scopes: scopes }];
const featuredGroups = []; const featuredGroups = [];
const groups = combineGroups(userGroups, featuredGroups, uri); const groups = combineGroups(userGroups, featuredGroups, uri);
groups.forEach(g => assert.equal(g.isScopedToUri, shouldBeSelectable)); groups.forEach(g => assert.equal(g.isScopedToUri, isScopedToUri));
}); });
}); });
......
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