Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
23c23d7
init
lucasgoral Aug 26, 2025
0c01a1a
Update ImportMembersDialog.tsx
lucasgoral Aug 27, 2025
6b440c0
updates
lucasgoral Aug 27, 2025
72ab035
Update ImportMembersDialog.tsx
lucasgoral Aug 27, 2025
d2ce22a
Update ImportMembersDialog.tsx
lucasgoral Aug 27, 2025
166f647
Update ImportMembersDialog.tsx
lucasgoral Aug 27, 2025
4a4dcc3
Update ImportMembersDialog.tsx
lucasgoral Aug 27, 2025
2931141
fixes
lucasgoral Aug 28, 2025
590a1fb
fixes
lucasgoral Aug 28, 2025
9b833dc
Update MemberTable.tsx
lucasgoral Aug 28, 2025
db1afff
Merge branch 'main' into import-members
lucasgoral Aug 28, 2025
421477e
Merge branch 'members-table-fix' into import-members
lucasgoral Aug 28, 2025
ba36f94
Update ImportMembersDialog.tsx
lucasgoral Aug 28, 2025
74b192d
refactor
lucasgoral Aug 28, 2025
402c410
fix
lucasgoral Aug 28, 2025
ccdd7a5
Update ImportMembersDialog.tsx
lucasgoral Aug 28, 2025
78a6fab
Merge branch 'main' into import-members
lucasgoral Aug 29, 2025
0ae01d9
Update ImportMembersDialog.tsx
lucasgoral Aug 29, 2025
3dd5531
Update ImportMembersDialog.tsx
lucasgoral Aug 29, 2025
6044067
fixes
lucasgoral Aug 29, 2025
781b873
fix
lucasgoral Aug 29, 2025
f62e443
suggest label and icon
GenosseOtt Aug 29, 2025
9511fdd
simplify members table
GenosseOtt Aug 29, 2025
4100f11
fixes
lucasgoral Aug 29, 2025
da3fdec
fix
lucasgoral Aug 29, 2025
e4b7dd4
Update MemberTable.tsx
lucasgoral Aug 29, 2025
63f7c3c
Merge branch 'main' into import-members
Lasserich Aug 29, 2025
0a17041
Merge branch 'main' into import-members
andreaskienle Sep 3, 2025
b47494d
Revamp Import Members dialog UI and logic
andreaskienle Sep 4, 2025
d4d3401
Merge branch 'main' into import-members
andreaskienle Sep 4, 2025
3aa41c9
Implement review feedback
andreaskienle Sep 4, 2025
503af2a
Add toast
andreaskienle Sep 4, 2025
e8b65f7
Fix linting
andreaskienle Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
},
"Entities": {
"ManagedControlPlane": "Managed Control Plane",
"Project": "Project"
"Project": "Project",
"Workspace": "Workspace",
"Users": "Users",
"ServiceAccounts": "ServiceAccounts"
},
"ComponentList": {
"tableComponentHeader": "Name",
Expand Down Expand Up @@ -142,7 +145,7 @@
"subtitle": "Fetching data..."
},
"MemberTable": {
"columnEmailHeader": "Email",
"columnNameHeader": "Name",
"columnRoleHeader": "Role",
"columnTypeHeader": "Type",
"columnNamespaceHeader": "Namespace"
Expand All @@ -165,12 +168,18 @@
"membersHeader": "Members"
},
"EditMembers": {
"addButton": "Add new member or service account",
"editHeader": "Edit member or service account",
"addHeader": "Add new member or service account",
"addButton": "Add User or ServiceAccount",
"editHeader": "Edit User or ServiceAccount",
"addHeader": "Add User or ServiceAccount",
"saveButton": "Save changes",
"defaultNamespaceInfo": "Leave empty to use <span>default</span> namespace",
"serviceAccoutsGuide": "You can also use our <link1>Service Account Guide</link1> for more information."
"serviceAccoutsGuide": "You can also use our <link1>Service Account Guide</link1> for more information.",
"reuseMembersButton": "Reuse",
"membersToastNoChanges": "No changes.",
"membersToastAdded1": "1 member added.",
"membersToastAddedN": "{{count}} members added.",
"membersToastChanged1": "1 member changed.",
"membersToastChangedN": "{{count}} members changed."
},

"ProjectsPage": {
Expand Down Expand Up @@ -315,6 +324,7 @@
"notValidChargingTargetFormat": "Use lowercase letters a-f, numbers 0-9, and hyphens (-) in the format: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
},
"common": {
"all": "All",
"documentation": "Documentation",
"close": "Close",
"cannotLoadData": "Cannot load data",
Expand Down Expand Up @@ -421,4 +431,13 @@
"activate": "Activate"
}
}
,
"ImportMembersDialog": {
"dialogTitle": "Reuse Members",
"reuseFromLabel": "Reuse from",
"filterForLabel": "Filter for",
"addMembersButton0": "Add members",
"addMembersButton1": "Add member",
"addMembersButtonN": "Add {{count}} members"
}
}
10 changes: 8 additions & 2 deletions src/components/Dialogs/CreateProjectWorkspaceDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface CreateProjectWorkspaceDialogProps {
errors: FieldErrors<CreateDialogProps>;
setValue: UseFormSetValue<CreateDialogProps>;
projectName?: string;
type: 'workspace' | 'project';
type: 'workspace' | 'project' | 'mcp';
watch: UseFormWatch<CreateDialogProps>;
}

Expand Down Expand Up @@ -93,7 +93,13 @@ export function CreateProjectWorkspaceDialog({
requireChargingTarget={type === 'project'}
sideFormContent={
<FormGroup headerText={t('CreateProjectWorkspaceDialog.membersHeader')}>
<EditMembers members={members} isValidationError={!!errors.members} onMemberChanged={setMembers} />
<EditMembers
type={type}
members={members}
isValidationError={!!errors.members}
projectName={projectName}
onMemberChanged={setMembers}
/>
</FormGroup>
}
/>
Expand Down
124 changes: 112 additions & 12 deletions src/components/Members/EditMembers.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
import { FC, useCallback, useState } from 'react';
import { FC, useCallback, useMemo, useState } from 'react';
import { Button, FlexBox } from '@ui5/webcomponents-react';
import { MemberTable } from './MemberTable.tsx';
import { Member } from '../../lib/api/types/shared/members';
import { areMembersEqual, Member } from '../../lib/api/types/shared/members';
import { useTranslation } from 'react-i18next';
import styles from './Members.module.css';
import { RadioButtonsSelectOption } from '../Ui/RadioButtonsSelect/RadioButtonsSelect.tsx';
import { AddEditMemberDialog } from './AddEditMemberDialog.tsx';
import { ImportMembersDialog } from './ImportMembersDialog.tsx';
import { useToast } from '../../context/ToastContext.tsx';
import { TFunction } from 'i18next';

export interface EditMembersProps {
members: Member[];
onMemberChanged: (members: Member[]) => void;
isValidationError?: boolean;
requireAtLeastOneMember?: boolean;
projectName?: string;
workspaceName?: string;
type: 'workspace' | 'project' | 'mcp';
}

export const ACCOUNT_TYPES: RadioButtonsSelectOption[] = [
{ value: 'User', label: 'User Account', icon: 'employee' },
{ value: 'User', label: 'User', icon: 'employee' },
{ value: 'ServiceAccount', label: 'Service Account', icon: 'machine' },
];

export type AccountType = 'User' | 'ServiceAccount';

const PROJECT_PREFIX = 'project-';
const removeProjectPrefix = (name?: string) =>
name?.startsWith(PROJECT_PREFIX) ? name.slice(PROJECT_PREFIX.length) : name;

export const EditMembers: FC<EditMembersProps> = ({
members,
onMemberChanged,
isValidationError = false,
requireAtLeastOneMember = true,
workspaceName,
projectName,
type,
}) => {
const { t } = useTranslation();

const [isMemberDialogOpen, setIsMemberDialogOpen] = useState(false);
const [memberToEdit, setMemberToEdit] = useState<Member | undefined>(undefined);
const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);

const handleRemoveMember = useCallback(
(email: string) => {
Expand All @@ -53,6 +67,39 @@ export const EditMembers: FC<EditMembersProps> = ({
setIsMemberDialogOpen(false);
}, []);

const handleOpenImportDialog = useCallback(() => {
setIsImportDialogOpen(true);
}, []);

const handleCloseImportDialog = useCallback(() => {
setIsImportDialogOpen(false);
}, []);

const toast = useToast();

const handleImportMembers = useCallback(
(imported: Member[]) => {
let numberOfAddedMembers = 0;
let numberOfChangedMembers = 0;

const membersByName = new Map<string, Member>(members.map((member) => [member.name, member]));
imported.forEach((importedMember) => {
const existingMember = membersByName.get(importedMember.name);
if (!existingMember) {
numberOfAddedMembers++;
} else if (!areMembersEqual(importedMember, existingMember)) {
numberOfChangedMembers++;
}
membersByName.set(importedMember.name, importedMember);
});
const updatedMembers = Array.from(membersByName.values());

toast.show(buildToastMessage(numberOfAddedMembers, numberOfChangedMembers, t));
onMemberChanged(updatedMembers);
},
[members, onMemberChanged, t, toast],
);

const handleSaveMember = useCallback(
(member: Member, isEdit: boolean) => {
let updatedMembers: Member[];
Expand All @@ -74,17 +121,34 @@ export const EditMembers: FC<EditMembersProps> = ({
[members, onMemberChanged, memberToEdit],
);

const computedProjectName = useMemo(
() => (type === 'mcp' ? removeProjectPrefix(projectName) : projectName),
[type, projectName],
);

return (
<FlexBox direction="Column" gap={8}>
<Button
className={styles.addButton}
data-testid="add-member-button"
design="Emphasized"
icon={'sap-icon://add-employee'}
onClick={handleOpenMemberFormDialog}
>
{t('EditMembers.addButton')}
</Button>
<FlexBox gap={8} justifyContent="SpaceBetween">
<Button
className={styles.addButton}
data-testid="add-member-button"
design="Emphasized"
icon={'sap-icon://add-employee'}
onClick={handleOpenMemberFormDialog}
>
{t('EditMembers.addButton')}
</Button>
{type !== 'project' && (
<Button
className={styles.narrowButton}
data-testid="import-members-button"
icon={'cause'}
onClick={handleOpenImportDialog}
>
{t('EditMembers.reuseMembersButton')}
</Button>
)}
</FlexBox>
<AddEditMemberDialog
open={isMemberDialogOpen}
existingMembers={members}
Expand All @@ -93,6 +157,16 @@ export const EditMembers: FC<EditMembersProps> = ({
onSave={handleSaveMember}
/>

{computedProjectName && (
<ImportMembersDialog
isOpen={isImportDialogOpen}
workspaceName={workspaceName}
projectName={computedProjectName}
onClose={handleCloseImportDialog}
onImport={handleImportMembers}
/>
)}

<MemberTable
requireAtLeastOneMember={requireAtLeastOneMember}
members={members}
Expand All @@ -103,3 +177,29 @@ export const EditMembers: FC<EditMembersProps> = ({
</FlexBox>
);
};

function buildToastMessage(addedCount: number, changedCount: number, t: TFunction) {
const messages: string[] = [];

if (addedCount === 0 && changedCount === 0) {
return t('EditMembers.membersToastNoChanges');
}

if (addedCount > 0) {
messages.push(
addedCount === 1
? t('EditMembers.membersToastAdded1')
: t('EditMembers.membersToastAddedN', { count: addedCount }),
);
}

if (changedCount > 0) {
messages.push(
changedCount === 1
? t('EditMembers.membersToastChanged1')
: t('EditMembers.membersToastChangedN', { count: changedCount }),
);
}

return messages.join(' ');
}
18 changes: 18 additions & 0 deletions src/components/Members/ImportMembersDialog.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.dialog {
min-width: 650px;
}

.grid {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
padding: 1rem 1rem 2rem;
}

.gridColumnLabel {
align-self: center;
}

.tableContainer {
padding: 1rem;
}
Loading
Loading