Skip to content

Commit

Permalink
Merge branch 'main' into invite-user-text
Browse files Browse the repository at this point in the history
  • Loading branch information
DaveDarsa authored Jul 12, 2024
2 parents acddc6c + 8fec6dc commit 56f4ab4
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 125 deletions.
3 changes: 1 addition & 2 deletions src/components/Organizations/GroupMembers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ const GroupMembers = ({
projects,
refetch,
}) => {
const duRegex = new RegExp('^default-user@' + groupName.replace('project-', '') + '$', 'g');

const [projectModalOpen, setProjectModalOpen] = useState(false);
const [selectedProject, setSelectedProject] = useState('');

Expand Down Expand Up @@ -356,6 +354,7 @@ const GroupMembers = ({
defaultViewOptions={{
type: 'user',
selected: false,
selectedOnZeroCount: true,
}}
/>
<div className="tableAction">
Expand Down
77 changes: 66 additions & 11 deletions src/components/Organizations/PaginatedTable/PaginatedTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import {
TableRow,
} from './Styles';

interface NestedData {
[key: string]: string | Record<string, string> | string[];
}

type DataType = {
[key: string]:
| string
| Record<string, string>
| Record<string, Pick<Props, 'data'> | string[]>
| Array<Record<string, string>>;
[key: string]: string | NestedData | Record<string, string> | Array<Record<string, string>>;
name: string;
id: string;
};
Expand All @@ -38,8 +38,13 @@ interface Props {
disableUrlMutation?: boolean;
defaultViewOptions?: {
type: 'group' | 'user';
selected: boolean;
};
} & (
| { selected: true }
| {
selected: false;
selectedOnZeroCount?: boolean;
}
);
numericSortOptions?: {
key?: string;
displayName: string;
Expand Down Expand Up @@ -100,9 +105,34 @@ const PaginatedTable: FC<Props> = ({

const [unfilteredData, setUnfilteredData] = useState(data);

const [defaultsSelected, setDefaultsSelected] = useState(
(defaultViewOptions && defaultViewOptions.selected) || false
);
const filteredDataWithoutDefaults = useMemo(() => {
let filtered = unfilteredData;

if (defaultViewOptions) {
if (defaultViewOptions.type === 'group') {
filtered = filtered.filter(dataItem => dataItem.type !== 'project-default-group');
}
if (defaultViewOptions.type === 'user') {
filtered = filtered.filter(dataItem => {
//@ts-ignore
const filterItem = dataItem.email ? dataItem.email : (dataItem.user.email as string);
return !(filterItem as string).startsWith('default-user');
});
}
}

return filtered;
}, [defaultViewOptions, unfilteredData]);

const [defaultsSelected, setDefaultsSelected] = useState(() => {
if (defaultViewOptions?.selected) {
return true;
}
if (defaultViewOptions?.selectedOnZeroCount && filteredDataWithoutDefaults.length === 0) {
return true;
}
return false;
});

useEffect(() => {
setUnfilteredData(data);
Expand Down Expand Up @@ -321,6 +351,31 @@ const PaginatedTable: FC<Props> = ({
const startPage = Math.max(currentPage - Math.floor(maxPagination / 2), 1);
const endPage = Math.min(startPage + maxPagination - 1, totalPages);

const systemDefaultCount = useMemo(() => {
let count = 0;

if (defaultViewOptions) {
if (defaultViewOptions?.type === 'group') {
count = unfilteredData.filter(dataItem => dataItem.type === 'project-default-group').length;
}
if (defaultViewOptions?.type === 'user') {
count = unfilteredData.filter(dataItem => {
let filterItem = '';

if (dataItem.email) {
filterItem = dataItem.email as string;
}
if (dataItem.user && typeof dataItem.user === 'object' && 'email' in dataItem.user) {
filterItem = dataItem.user.email as string;
}

return filterItem.startsWith('default-user');
}).length;
}
}
return count;
}, [defaultViewOptions, unfilteredData]);

return (
<StyledTable className="paginatedTable">
<Filters className="filters">
Expand All @@ -333,7 +388,7 @@ const PaginatedTable: FC<Props> = ({
)}
{defaultViewOptions ? (
<Checkbox>
{defaultViewOptions.type === 'group' ? 'Show system groups' : 'Show default users'}
{defaultViewOptions.type === 'group' ? 'Show system groups' : 'Show default users'} ({systemDefaultCount})
<input
type="checkbox"
checked={defaultsSelected}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React from 'react';
import Skeleton from 'react-loading-skeleton';

import { SearchOutlined } from '@ant-design/icons';

import { SearchBar } from '../Orgheader/Styles';
import { StyledGroupMembers } from './Styles';

const ProjectGroupSkeleton = () => {
Expand All @@ -24,10 +27,15 @@ const ProjectGroupSkeleton = () => {
);
return (
<StyledGroupMembers>
<div className="header" style={{ marginTop: '20px', paddingRight: '0' }}>
<label style={{ paddingLeft: '0' }}>Groups</label>
<input aria-labelledby="search" className="searchInput" type="text" placeholder="Type to search" disabled />
<div className="tableheader skeleton">
<label>Groups</label>

<SearchBar className="search">
<SearchOutlined className="icon" />
<input aria-labelledby="search" className="searchInput" type="text" placeholder="Type to search" disabled />
</SearchBar>
</div>

<div className="data-table">{[...Array<undefined>(numberOfFields)].map((_, idx) => groupsSkeleton(idx))}</div>
</StyledGroupMembers>
);
Expand Down
9 changes: 9 additions & 0 deletions src/components/Organizations/ProjectGroupMembers/Styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import styled from 'styled-components';
import { sharedTableStyles } from '../SharedStyles';

export const StyledGroupMembers = styled.div`
.project-wrapper {
margin-top: 20px;
}
.skeleton {
margin-top: 40px;
input {
padding-left: 30px;
}
}
.default-group-label {
color: ${color.white};
display: inline-block;
Expand Down
172 changes: 83 additions & 89 deletions src/components/Organizations/ProjectGroupMembers/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import { Mutation } from 'react-apollo';

import { EyeOutlined } from '@ant-design/icons';
Expand All @@ -8,7 +8,8 @@ import OrgGroupsLink from 'components/link/Organizations/Group';
import gql from 'graphql-tag';

import AddGroupToProject from '../AddGroupToProject';
import { Checkbox } from '../PaginatedTable/Styles';
import PaginatedTable from '../PaginatedTable/PaginatedTable';
import { TableActions, Tag } from '../SharedStyles';
import { StyledGroupMembers } from './Styles';

const REMOVE_GROUP_FROM_PROJECT = gql`
Expand All @@ -31,87 +32,62 @@ const ProjectGroupMembers = ({
refresh,
orgFriendlyName,
}) => {
const [searchInput, setSearchInput] = useState('');

const [showDefaults, setShowDefaults] = useState(false);

const filteredGroups = showDefaults ? groups : groups.filter(group => group.type !== 'project-default-group');

const filteredMembers = filteredGroups.filter(key => {
const sortByName = key.name.toLowerCase().includes(searchInput.toLowerCase());
return ['name', 'role', '__typename'].includes(key) ? false : true && sortByName;
});

useEffect(() => {
// tick the "show system groups" box if all groups are of that type.
const allDefaults = filteredMembers.every(group => group.type === 'project-default-group');
if (allDefaults) setShowDefaults(true);
}, []);

return (
<StyledGroupMembers>
<div className="header" style={{ marginTop: '20px', paddingRight: '0' }}>
<label style={{ paddingLeft: '0' }}>Groups ({filteredMembers.length})</label>
<Checkbox>
Show system groups
<input
type="checkbox"
checked={showDefaults}
onChange={({ target: { checked } }) => setShowDefaults(checked)}
/>
</Checkbox>

<input
aria-labelledby="search"
className="searchInput"
type="text"
value={searchInput}
onChange={e => setSearchInput(e.target.value)}
placeholder="Type to search"
disabled={groups.length === 0}
/>
</div>

<div className="data-table">
{!filteredMembers.length && <div className="data-none">No groups</div>}
{searchInput && !filteredMembers.length && <div className="data-none">No groups matching "{searchInput}"</div>}
{filteredMembers.map(group => (
<div className="data-row" key={group.id}>
<div className="name">
<OrgGroupsLink
groupSlug={group.name}
orgFriendlyName={orgFriendlyName}
organizationId={organizationId}
organizationSlug={organizationSlug}
>
{group.name}
</OrgGroupsLink>
</div>
<div className="members">Members: {group.memberCount}</div>

<div className="labels">
{group.type.includes('project-default-group') && (
<label className="default-group-label">SYSTEM GROUP</label>
const Columns = [
{
width: '50%',
key: 'name',
render: g => {
return (
<OrgGroupsLink
groupSlug={g.name}
organizationSlug={organizationSlug}
organizationId={organizationId}
orgFriendlyName={orgFriendlyName}
key={g.id}
>
<span>
{g.name}{' '}
{g.type === 'project-default-group' && (
<Tag style={{ display: 'inline' }} className="default-group-label" $background="#262D65">
SYSTEM GROUP
</Tag>
)}
</div>
</span>
</OrgGroupsLink>
);
},
},

<div className="actions">
<Tooltip overlayClassName="orgTooltip" placement="bottom" title="View group">
<div className="link">
<>
<OrgGroupsLink
orgFriendlyName={orgFriendlyName}
groupSlug={group.name}
organizationSlug={organizationSlug}
organizationId={organizationId}
>
<EyeOutlined />
</OrgGroupsLink>
</>
</div>
</Tooltip>
{/* even though we can't prevent users from removing the project default group from the api, we can make it harder to do from the ui */}
{!group.name.includes('project-' + projectName.toLowerCase()) ? (
{
width: '15%',
key: 'members',
render: g => {
return typeof g.memberCount !== 'undefined' && <span data-cy="memberCount">Members: {g.memberCount} </span>;
},
},
{
width: '35%',
key: 'actions',
render: function (g) {
return (
<TableActions>
<Tooltip overlayClassName="orgTooltip" placement="bottom" title="View group">
<div className="link">
<>
<OrgGroupsLink
orgFriendlyName={orgFriendlyName}
groupSlug={g.name}
organizationSlug={organizationSlug}
organizationId={organizationId}
>
<EyeOutlined />
</OrgGroupsLink>
</>
</div>
</Tooltip>

{g.type !== 'project-default-group' && (
<>
<div className="remove">
<Mutation mutation={REMOVE_GROUP_FROM_PROJECT}>
{(removeGroupFromProject, { _, called, error }) => {
Expand All @@ -123,13 +99,13 @@ const ProjectGroupMembers = ({
loading={called}
info={{
type: 'group',
deleteName: group.name,
deleteName: g.name,
projectName: projectName,
}}
onRemove={() => {
removeGroupFromProject({
variables: {
groupName: group.name,
groupName: g.name,
projectName: projectName,
},
}).then(refresh);
Expand All @@ -139,13 +115,31 @@ const ProjectGroupMembers = ({
}}
</Mutation>
</div>
) : (
<div className="remove"></div>
)}
</div>
</div>
))}
</>
)}
</TableActions>
);
},
},
];

return (
<StyledGroupMembers>
<div className="project-wrapper">
<PaginatedTable
limit={10}
data={groups}
columns={Columns}
defaultViewOptions={{
selected: false,
selectedOnZeroCount: true,
type: 'group',
}}
emptyText="No groups found"
labelText="Groups"
/>
</div>

<AddGroupToProject
projectName={projectName}
organizationId={organizationId}
Expand Down
Loading

0 comments on commit 56f4ab4

Please sign in to comment.