Unverified Commit cf87bd52 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1195 from hypothesis/remove-old-groups-menu

Remove old groups menu and "community_groups" feature flag checks
parents fe386238 c3269060
'use strict';
const { isThirdPartyUser } = require('../util/account-id');
const isThirdPartyService = require('../util/is-third-party-service');
const serviceConfig = require('../service-config');
const memoize = require('../util/memoize');
const groupsByOrganization = require('../util/group-organizations');
const groupOrganizations = memoize(groupsByOrganization);
// @ngInject
function GroupListController(
$window,
analytics,
features,
groups,
settings,
serviceUrl
) {
this.groups = groups;
this.createNewGroup = function() {
$window.open(serviceUrl('groups.new'), '_blank');
};
this.focusedIcon = function() {
const focusedGroup = this.groups.focused();
return (
focusedGroup &&
(focusedGroup.organization.logo || this.thirdPartyGroupIcon)
);
};
this.focusedIconClass = function() {
const focusedGroup = this.groups.focused();
return focusedGroup && focusedGroup.type === 'private' ? 'group' : 'public';
};
this.isThirdPartyUser = function() {
return isThirdPartyUser(this.auth.userid, settings.authDomain);
};
this.leaveGroup = function(groupId) {
const groupName = groups.get(groupId).name;
const message =
'Are you sure you want to leave the group "' + groupName + '"?';
if ($window.confirm(message)) {
analytics.track(analytics.events.GROUP_LEAVE);
groups.leave(groupId);
}
};
this.orgName = function(groupId) {
const group = this.groups.get(groupId);
return group && group.organization && group.organization.name;
};
this.groupOrganizations = function() {
return groupOrganizations(this.groups.all());
};
this.viewGroupActivity = function() {
analytics.track(analytics.events.GROUP_VIEW_ACTIVITY);
};
this.focusGroup = function(groupId) {
analytics.track(analytics.events.GROUP_SWITCH);
groups.focus(groupId);
};
/**
* Show the share link for the group if it is not a third-party group
* AND if the URL needed is present in the group object. We should be able
* to simplify this once the API is adjusted only to return the link
* when applicable.
*/
this.shouldShowActivityLink = function(groupId) {
const group = groups.get(groupId);
return group.links && group.links.html && !this.isThirdPartyService;
};
const svc = serviceConfig(settings);
if (svc && svc.icon) {
this.thirdPartyGroupIcon = svc.icon;
}
this.isThirdPartyService = isThirdPartyService(settings);
this.showGroupsMenu = () => {
if (features.flagEnabled('community_groups')) {
// Only show the drop down menu if there is more than one group.
return this.groups.all().length > 1;
} else {
return !(this.isThirdPartyService && this.groups.all().length <= 1);
}
};
/**
* Expose the feature flag so it can be used in the template logic to show
* or hide the new groups menu.
*/
this.isFeatureFlagEnabled = flag => {
return features.flagEnabled(flag);
};
}
module.exports = {
controller: GroupListController,
controllerAs: 'vm',
bindings: {
auth: '<',
},
template: require('../templates/group-list.html'),
};
This diff is collapsed.
...@@ -161,9 +161,8 @@ function startAngularApp(config) { ...@@ -161,9 +161,8 @@ function startAngularApp(config) {
require('./components/annotation-viewer-content') require('./components/annotation-viewer-content')
) )
.component('excerpt', require('./components/excerpt')) .component('excerpt', require('./components/excerpt'))
.component('groupList', require('./components/group-list'))
.component( .component(
'groupListV2', 'groupList',
wrapReactComponent(require('./components/group-list-v2')) wrapReactComponent(require('./components/group-list-v2'))
) )
.component( .component(
......
...@@ -16,7 +16,6 @@ const DEFAULT_ORGANIZATION = { ...@@ -16,7 +16,6 @@ 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
...@@ -29,8 +28,7 @@ function groups( ...@@ -29,8 +28,7 @@ function groups(
serviceUrl, serviceUrl,
session, session,
settings, settings,
auth, auth
features
) { ) {
const svc = serviceConfig(settings); const svc = serviceConfig(settings);
const authority = svc ? svc.authority : null; const authority = svc ? svc.authority : null;
...@@ -299,26 +297,9 @@ function groups( ...@@ -299,26 +297,9 @@ function groups(
return groups; return 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) {
......
...@@ -41,7 +41,6 @@ const dummyGroups = [ ...@@ -41,7 +41,6 @@ const dummyGroups = [
describe('groups', function() { describe('groups', function() {
let fakeAuth; let fakeAuth;
let fakeFeatures;
let fakeStore; let fakeStore;
let fakeIsSidebar; let fakeIsSidebar;
let fakeSession; let fakeSession;
...@@ -55,9 +54,6 @@ describe('groups', function() { ...@@ -55,9 +54,6 @@ describe('groups', function() {
fakeAuth = { fakeAuth = {
tokenGetter: sinon.stub().returns('1234'), tokenGetter: sinon.stub().returns('1234'),
}; };
fakeFeatures = {
flagEnabled: sinon.stub().returns(false),
};
fakeStore = fakeReduxStore( fakeStore = fakeReduxStore(
{ {
...@@ -146,42 +142,17 @@ describe('groups', function() { ...@@ -146,42 +142,17 @@ describe('groups', function() {
fakeServiceUrl, fakeServiceUrl,
fakeSession, fakeSession,
fakeSettings, fakeSettings,
fakeAuth, fakeAuth
fakeFeatures
); );
} }
describe('#all', function() { describe('#all', function() {
it('returns all groups from store.allGroups when community-groups feature flag is enabled', () => { it('returns all groups from store.allGroups', () => {
const svc = service(); const svc = service();
fakeStore.allGroups = sinon.stub().returns(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); 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() {
......
<div
class="pull-right"
dropdown
keyboard-nav
ng-if="!vm.isFeatureFlagEnabled('community_groups')"
>
<!-- Show a drop down menu if showGroupsMenu is true. -->
<div
class="dropdown-toggle"
dropdown-toggle
data-toggle="dropdown"
role="button"
tabindex="0"
ng-if="vm.showGroupsMenu()"
>
<img
class="group-list-label__icon group-list-label__icon--organization"
ng-src="{{ vm.focusedIcon() }}"
alt="{{ vm.orgName(vm.groups.focused().id)}}"
ng-if="vm.focusedIcon()"
/>
<i
class="group-list-label__icon h-icon-{{ vm.focusedIconClass() }}"
ng-if="!vm.focusedIcon()"
></i
><!-- nospace
!--><span class="group-list-label__label"
><a class="group-list-label__toggle">{{vm.groups.focused().name}}</a
><!-- nospace
!--><i class="h-icon-arrow-drop-down"></i
></span>
</div>
<!-- Do not show a drop down menu if showGroupsMenu is false. -->
<div ng-if="!vm.showGroupsMenu()">
<img
class="group-list-label__icon group-list-label__icon--organization"
ng-src="{{ vm.focusedIcon() }}"
alt="{{ vm.orgName(vm.groups.focused().id)}}"
ng-if="vm.focusedIcon()"
/>
<i
class="group-list-label__icon h-icon-{{ vm.focusedIconClass() }}"
ng-if="!vm.focusedIcon()"
></i
><!-- nospace
!--><span class="group-list-label__label"
>{{vm.groups.focused().name}}</span
>
</div>
<!-- Only build the drop down menu if showGroupsMenu is true. -->
<!-- Original groups menu. -->
<div
class="dropdown-menu__top-arrow"
ng-if="vm.showGroupsMenu() && !vm.isFeatureFlagEnabled('community_groups')"
></div>
<ul
class="dropdown-menu pull-none"
role="menu"
ng-if="vm.showGroupsMenu() && !vm.isFeatureFlagEnabled('community_groups')"
>
<li
class="dropdown-menu__row dropdown-menu__row--unpadded "
ng-repeat="group in vm.groupOrganizations() track by group.id"
>
<div
ng-class="{'group-item': true, selected: group.id == vm.groups.focused().id}"
ng-click="vm.focusGroup(group.id)"
>
<!-- the group icon !-->
<div class="group-menu-icon-container">
<img
class="group-list-label__icon group-list-label__icon--organization"
alt="{{ vm.orgName(group.id) }}"
ng-src="{{ group.logo }}"
ng-if="group.logo"
/>
</div>
<!-- the group name and share link !-->
<div class="group-details">
<div class="group-name-container">
<a
class="group-name-link"
href=""
title="{{ group.type === 'private' ? 'Show and create annotations in ' + group.name : 'Show public annotations' }}"
>
{{group.name}}
</a>
</div>
<div
class="share-link-container"
ng-click="$event.stopPropagation()"
ng-if="vm.shouldShowActivityLink(group.id)"
>
<a
class="share-link"
href="{{group.links.html}}"
target="_blank"
ng-click="vm.viewGroupActivity()"
>
View group activity
<span ng-if="group.type === 'private'">
and invite others
</span>
</a>
</div>
</div>
<!-- the 'Leave group' icon !-->
<div
class="group-cancel-icon-container"
ng-click="$event.stopPropagation()"
>
<i
class="h-icon-cancel-outline btn--cancel"
ng-if="group.type === 'private'"
ng-click="vm.leaveGroup(group.id)"
title="Leave '{{group.name}}'"
></i>
</div>
</div>
</li>
<li
ng-if="vm.auth.status === 'logged-in' && !vm.isThirdPartyUser()"
class="dropdown-menu__row dropdown-menu__row--unpadded new-group-btn"
>
<div class="group-item" ng-click="vm.createNewGroup()">
<div class="group-icon-container"><i class="h-icon-add"></i></div>
<div class="group-details">
<a
href=""
class="group-name-link"
title="Create a new group to share annotations"
>
New private group
</a>
</div>
</div>
</li>
</ul>
</div>
<group-list-v2
ng-if="vm.isFeatureFlagEnabled('community_groups')"
></group-list-v2>
/* The group-list out of scope item. */
.group-list-item-out-of-scope {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.group-list-item-out-of-scope__item {
background-color: $gray-lightest;
color: $gray-light;
}
.group-list-item-out-of-scope__icon--unavailable {
fill: $gray-light;
float: right;
height: 20px;
width: auto;
}
.group-list-item-out-of-scope__details {
flex-grow: 1;
flex-shrink: 1;
font-weight: 500;
}
.group-list-item-out-of-scope__details-toggle {
font-size: $body1-font-size;
font-style: italic;
margin: 0;
text-decoration: underline;
}
.group-list-item-out-of-scope__details-unavailable-message {
font-size: $body1-font-size;
line-height: 1.5;
white-space: normal;
width: $group-list-width - 60px;
}
.group-list-item-out-of-scope__details-actions {
text-align: right;
}
.group-list-item-out-of-scope__details-group-page-link {
color: inherit;
font-size: $body1-font-size;
text-decoration: underline;
text-transform: uppercase;
}
/* The groups dropdown list. */
$group-list-spacing-below: 50px;
.group-list {
.dropdown {
white-space: nowrap;
}
.dropdown-menu {
width: $group-list-width;
max-height: 500px; // fallback for browsers lacking support for vh/calc
max-height: calc(100vh - #{$top-bar-height} - #{$group-list-spacing-below});
overflow-y: auto;
.group-name {
overflow: hidden;
text-overflow: ellipsis;
width: $group-list-width - 30px;
}
}
.dropdown-menu__section--no-header {
.group-details {
font-weight: 400;
}
}
.group-item {
display: flex;
flex-direction: row;
flex-grow: 1;
padding: 10px;
cursor: pointer;
&:hover {
.group-name-link {
color: $brand-color;
}
}
&.selected {
.group-name-link {
font-size: $body2-font-size;
font-weight: 600;
}
}
}
.group-icon-container {
margin-right: 10px;
}
.group-menu-icon-container {
margin-right: 10px;
width: 15px;
height: 15px;
}
.group-cancel-icon-container {
// the 'Leave group' icon is shifted down slightly
// so that it lines up vertically with the 'chat heads' icon on the
// left-hand side of the groups list
padding-top: 3px;
margin-right: 2px;
}
.group-details {
flex-grow: 1;
flex-shrink: 1;
}
.new-group-btn {
background-color: $gray-lightest;
.group-item {
padding-top: 12px;
padding-bottom: 12px;
}
.h-icon-add {
font-weight: bold;
}
}
}
// the icon indicating the type of group currently selected at
// the top of the groups list
.group-list-label__icon {
color: $color-gray;
display: inline-block;
margin-right: 4px;
position: relative;
vertical-align: baseline;
// align the base of the chat-heads icon for groups
// with the baseline of the group name label
transform: translateY(1px);
}
.group-list-label__icon--organization {
height: 15px;
width: 15px;
top: 2px;
}
// the label showing the currently selected group which opens
// the drop-down list when clicked
.group-list-label__label {
font-size: $body2-font-size;
font-weight: bold;
display: inline-block;
}
// the anchor showing the name of the currently selected group
.group-list-label__toggle {
color: $gray-dark;
}
// the name of a group in the groups drop-down list
// and 'Post to <Group>' button for saving annotations
.group-name-link {
white-space: nowrap;
color: inherit;
}
.open {
& > .group-list__toggle {
background: $gray-lighter;
}
}
...@@ -23,10 +23,8 @@ $base-line-height: 20px; ...@@ -23,10 +23,8 @@ $base-line-height: 20px;
@import './components/annotation-publish-control'; @import './components/annotation-publish-control';
@import './components/annotation-thread'; @import './components/annotation-thread';
@import './components/excerpt'; @import './components/excerpt';
@import './components/group-list';
@import './components/group-list-v2'; @import './components/group-list-v2';
@import './components/group-list-item'; @import './components/group-list-item';
@import './components/group-list-item-out-of-scope';
@import './components/help-panel'; @import './components/help-panel';
@import './components/loggedout-message'; @import './components/loggedout-message';
@import './components/login-control'; @import './components/login-control';
......
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