Skip to content

Commit

Permalink
feat/teaming: Added revamped UI of teaming for litmus 3.0.0 (#4134)
Browse files Browse the repository at this point in the history
* Added UI screens for active project members

Signed-off-by: Saranya-jena <[email protected]>

* Added auth endpoints to fetch active and pending members

Signed-off-by: Saranya-jena <[email protected]>

* Added api integration for active and pending members

Signed-off-by: Saranya-jena <[email protected]>

* Added api integration for invite users

Signed-off-by: Saranya-jena <[email protected]>

* Api refactor for invite users

Signed-off-by: Saranya-jena <[email protected]>

* Integrated table v2 with invite users api

Signed-off-by: Saranya-jena <[email protected]>

* Adding pending mebers screen

Signed-off-by: Saranya-jena <[email protected]>

* Added pending members table

Signed-off-by: Saranya-jena <[email protected]>

* Added json schema for all the fields

Signed-off-by: Saranya-jena <[email protected]>

* updated schema in frontend

Signed-off-by: Saranya-jena <[email protected]>

* Added epi integration for teaming

Signed-off-by: Saranya-jena <[email protected]>

* Added refetch functions in the modals and tables

Signed-off-by: Saranya-jena <[email protected]>

* Added ui fixes

Signed-off-by: Saranya-jena <[email protected]>

* Added loader and default text for empty array

Signed-off-by: Saranya-jena <[email protected]>

* Added user details fields while sending invitations

Signed-off-by: Saranya-jena <[email protected]>

* Added user details fields while project initialization

Signed-off-by: Saranya-jena <[email protected]>

* removed unused comments

Signed-off-by: Saranya-jena <[email protected]>

* resolved review comments

Signed-off-by: Saranya-jena <[email protected]>

* uncommented the logs

Signed-off-by: Saranya-jena <[email protected]>

---------

Signed-off-by: Saranya-jena <[email protected]>
  • Loading branch information
Saranya-jena authored Aug 21, 2023
1 parent 92d2dbe commit 4b778f5
Show file tree
Hide file tree
Showing 37 changed files with 1,058 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ func SendInvitation(service services.ApplicationService) gin.HandlerFunc {
newMember := &entities.Member{
UserID: user.ID,
Role: *member.Role,
Username: user.Username,
Name: user.Name,
Email: user.Email,
Invitation: entities.PendingInvitation,
JoinedAt: time.Now().Unix(),
}
Expand Down Expand Up @@ -499,7 +502,7 @@ func RemoveInvitation(service services.ApplicationService) gin.HandlerFunc {

case entities.DeclinedInvitation, entities.ExitedProject:
{
c.JSON(400, gin.H{"message": "User is already not a part of your project"})
c.JSON(400, gin.H{"message": "User is not a part of your project"})
return
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func InviteUsers(service services.ApplicationService) gin.HandlerFunc {
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}
c.JSON(200, users)
c.JSON(200, gin.H{"data": users})
}
}

Expand Down Expand Up @@ -224,6 +224,9 @@ func LoginUser(service services.ApplicationService) gin.HandlerFunc {
UserID: user.ID,
Role: entities.RoleOwner,
Invitation: entities.AcceptedInvitation,
Username: user.Username,
Name: user.Name,
Email: user.Email,
JoinedAt: time.Now().Unix(),
}
var members []*entities.Member
Expand Down
1 change: 1 addition & 0 deletions chaoscenter/authentication/pkg/project/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ func (r repository) RemoveInvitation(projectID string, userID string, invitation

result, err := r.Collection.UpdateOne(context.TODO(), query, update)
if err != nil {
// TODO check it's usage
if invitation == entities.AcceptedInvitation {
return err
}
Expand Down
1 change: 1 addition & 0 deletions chaoscenter/web/src/components/SideNav/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export default function SideNav(): ReactElement {
<SidebarLink label={'Chaos Experiments'} to={paths.toExperiments()} />
<SidebarLink label={'ChaosHubs'} to={paths.toChaosHubs()} />
<SidebarLink label={'Environments'} to={paths.toEnvironments()} />
<SidebarLink label={'Members'} to={paths.toProjectMembers()} />
</Layout.Vertical>
</div>
<Container className={css.bottomContainer}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useParams } from 'react-router-dom';
import React from 'react';
import ActiveMembersTableView from '@views/ProjectMembers/ActiveMembersTable';
import { useGetProjectMembersQuery } from '@api/auth';

export default function ActiveProjectMembersController(): React.ReactElement {
const { projectID } = useParams<{ projectID: string }>();
const {
data,
isLoading,
refetch: getMembersRefetch
} = useGetProjectMembersQuery({ project_id: projectID, state: 'accepted' });

return (
<ActiveMembersTableView activeMembers={data?.data} isLoading={isLoading} getMembersRefetch={getMembersRefetch} />
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ActiveProjectMembersController from './ActiveProjectMembers';

export default ActiveProjectMembersController;
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import { ExpandingSearchInput } from '@harnessio/uicore';
import { useStrings } from '@strings';
import { useGetUsersForInvitationQuery, User } from '@api/auth';
import InviteNewMembersView from '@views/InviteNewMembers/InviteNewMembers';
import type { ProjectPathParams } from '@routes/RouteInterfaces';

interface InviteUsersControllerProps {
handleClose: () => void;
}

export default function InviteUsersController({ handleClose }: InviteUsersControllerProps): React.ReactElement {
const { projectID } = useParams<ProjectPathParams>();
const { data, isLoading, refetch: getUsers } = useGetUsersForInvitationQuery({ project_id: projectID });
const { getString } = useStrings();

const [searchQuery, setSearchQuery] = React.useState('');
const searchInput = (
<ExpandingSearchInput placeholder={getString('search')} alwaysExpanded onChange={value => setSearchQuery(value)} />
);

function doesFilterCriteriaMatch(user: User): boolean {
const updatedSearchQuery = searchQuery.trim();
if (
user.name?.toLowerCase().includes(updatedSearchQuery.toLowerCase()) ||
user.username?.toLowerCase().includes(updatedSearchQuery.toLowerCase()) ||
user.email?.toLowerCase().includes(updatedSearchQuery.toLowerCase())
)
return true;
return false;
}

const filteredData = React.useMemo(() => {
if (!data?.data) return [];
return data.data.filter(user => doesFilterCriteriaMatch(user));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data, searchQuery]);

return (
<InviteNewMembersView
data={filteredData}
getUsers={getUsers}
handleClose={handleClose}
isLoading={isLoading}
searchInput={searchInput}
/>
);
}
17 changes: 17 additions & 0 deletions chaoscenter/web/src/controllers/InviteNewMembers/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { User } from '@api/auth';
import type { InviteUserDetails } from './types';

export function generateInviteUsersTableContent(userData: Array<User> | undefined): Array<InviteUserDetails> {
const content: Array<InviteUserDetails> =
userData && userData.length > 0
? userData.map(user => {
return {
id: user.userID,
username: user.username,
email: user.email ?? '',
name: user.name ?? ''
};
})
: [];
return content;
}
3 changes: 3 additions & 0 deletions chaoscenter/web/src/controllers/InviteNewMembers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import InviteUsersController from './InviteNewMembers';

export default InviteUsersController;
6 changes: 6 additions & 0 deletions chaoscenter/web/src/controllers/InviteNewMembers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface InviteUserDetails {
username: string;
id: string;
email: string;
name: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useParams } from 'react-router-dom';
import React from 'react';
import PendingMembersTableView from '@views/ProjectMembers/PendingMembersTable';
import { useGetProjectMembersQuery } from '@api/auth';

export default function PendingProjectMembersController(): React.ReactElement {
const { projectID } = useParams<{ projectID: string }>();
const {
data,
isLoading,
refetch: getPendingMembersRefetch
} = useGetProjectMembersQuery({ project_id: projectID, state: 'not_accepted' });

return (
<PendingMembersTableView
pendingMembers={data?.data}
isLoading={isLoading}
getPendingMembersRefetch={getPendingMembersRefetch}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type PendingProjectMembersController from './PendingProjectMembers';

export default PendingProjectMembersController;
38 changes: 38 additions & 0 deletions chaoscenter/web/src/controllers/RemoveMember/RemoveMember.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { useToaster } from '@harnessio/uicore';
import type { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
import { GetProjectMembersOkResponse, useRemoveInvitationMutation } from '@api/auth';
import RemoveMemberView from '@views/RemoveMember';

interface RemoveMemberControllerProps {
userID: string;
username: string;
hideDeleteModal: () => void;
getMembersRefetch: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<GetProjectMembersOkResponse, unknown>>;
}
export default function RemoveMemberController(props: RemoveMemberControllerProps): React.ReactElement {
const { userID, username, hideDeleteModal, getMembersRefetch } = props;
const { showSuccess } = useToaster();

const { mutate: removeMemberMutation } = useRemoveInvitationMutation(
{},
{
onSuccess: data => {
getMembersRefetch();
showSuccess(data.message);
}
}
);

return (
<RemoveMemberView
{...props}
removeMemberMutation={removeMemberMutation}
userID={userID}
username={username}
handleClose={hideDeleteModal}
/>
);
}
1 change: 1 addition & 0 deletions chaoscenter/web/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export * from './chaosStudio';
export * from './license';
export * from './rbac';
export * from './user';
export * from './projectMembers';
export * from './token';
export * from './projects';
4 changes: 4 additions & 0 deletions chaoscenter/web/src/models/projectMembers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum MembersTabs {
ACTIVE = 'active-members',
PENDING = 'pending-members'
}
6 changes: 5 additions & 1 deletion chaoscenter/web/src/routes/RouteDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export interface UseRouteDefinitionsProps {
toChaosInfrastructures(params: { environmentID: string }): string;
toKubernetesChaosInfrastructures(params: { environmentID: string }): string;
toKubernetesChaosInfrastructureDetails(params: { chaosInfrastructureID: string; environmentID: string }): string;
// Project scoped
toProjectMembers(): string;
// Account Scoped Routes
toAccountSettingsOverview(): string;
}
Expand Down Expand Up @@ -56,5 +58,7 @@ export const paths: UseRouteDefinitionsProps = {
toKubernetesChaosInfrastructureDetails: ({ chaosInfrastructureID, environmentID }) =>
`/environments/${environmentID}/kubernetes/${chaosInfrastructureID}`,
// Account Scoped Routes
toAccountSettingsOverview: () => '/settings/overview'
toAccountSettingsOverview: () => '/settings/overview',
// user route
toProjectMembers: () => '/members'
};
3 changes: 3 additions & 0 deletions chaoscenter/web/src/routes/RouteDestinations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getUserDetails } from '@utils';
import EnvironmentController from '@controllers/Environments';
import { isUserAuthenticated } from 'utils/auth';
import AccountSettingsController from '@controllers/AccountSettings';
import ProjectMembersView from '@views/ProjectMembers';

const experimentID = ':experimentID';
const runID = ':runID';
Expand Down Expand Up @@ -107,6 +108,8 @@ export function RoutesWithAuthentication(): React.ReactElement {
path={projectMatchPaths.toKubernetesChaosInfrastructureDetails({ environmentID, chaosInfrastructureID })}
component={KubernetesChaosInfrastructureDetailsController}
/>
{/* Project */}
<Route exact path={projectMatchPaths.toProjectMembers()} component={ProjectMembersView} />
</Switch>
);
}
Expand Down
3 changes: 3 additions & 0 deletions chaoscenter/web/src/routes/RouteInterfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ProjectPathParams {
projectID: string;
}
16 changes: 16 additions & 0 deletions chaoscenter/web/src/strings/strings.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ account: Account
accountURL: Account URL
actionItems: Action Items
active: Active
activeMembers: Active members
actualResilienceScore: Actual Resilience Score
add: Add
addExperimentToChaosHub: Add Experiment to ChaosHub
Expand Down Expand Up @@ -425,7 +426,10 @@ intervalOptional: Interval (Optional)
invalidEmailText: Please enter a valid email address
invalidSelection: Invalid Selection
invalidText: Invalid
invitationRemoveSuccess: Invitation cancelled successfully
invitationSuccess: Invitation sent successfully
invitations: Invitations
inviteAs: Invite as
invitedBy: Invited By
isRequired: '{{field}} is required field'
issueSupport: If the issue still persists, consider reaching out to support!
Expand Down Expand Up @@ -490,6 +494,8 @@ manualInterruption: Manual Interruption needed
markRunAsComplete: Mark Run as Complete
markRunAsCompleteDescription: This will mark the run as complete and you will no longer be able to re-run.
meetsExpectations: The resilience score has met the expectation.
membersNotAvailableMessage: No active project members available
membersNotAvailableTitle: No members present
menuItems:
deleteHub: Delete Hub
editHub: Edit Hub
Expand Down Expand Up @@ -534,6 +540,7 @@ newChaosExperiment: New Chaos Experiment
newChaosHub: New ChaosHub
newChaosInfrastructure: Enable Chaos
newExperiment: New Experiment
newMember: New Member
newPassword: New Password
newProbe: New Probe
newUpdates: New Updates
Expand Down Expand Up @@ -644,6 +651,9 @@ passwordResetSuccess: Password reset successfully
passwordsDoNotMatch: Passwords do not match
pauseRun: Pause Run
pending: Pending
pendingInvitationsNotAvailableMessage: No pending invitations present
pendingInvitationsNotAvailableTitle: No invitations present
pendingMembers: Pending members
pendingTime: >-
It may take 3-5 minutes after applying the manifest for the infrastructure to
be connected.
Expand Down Expand Up @@ -749,8 +759,12 @@ recurringRun: Recurring Run (Cron)
recurringSchedule: Recurring Schedule
referencedBy: Referenced By
registryName: Registry Name
remove: Remove
removeMember: Remove Member
removeMemberConfirmation: Are you sure you want to remove {{username}}?
required: Required
rerun: Rerun
resend: Resend
resetFilters: Reset Filters
resetPassword: Reset Password
resilienceOverview: Resilience Overview
Expand Down Expand Up @@ -963,6 +977,8 @@ userCreatedOn: User Created On
userManagement: User Management
username: Username
usernameIsRequired: Username is a required field
usersNotAvailableMessage: No users available to send invitation
usersNotAvailableTitle: No users available
validationError: Validation Error
value: Value
valuePlaceholder: Enter a value
Expand Down
Loading

0 comments on commit 4b778f5

Please sign in to comment.