Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8680aae
Added missing padding around the Managed dashboard card
fabrice-akamai Sep 26, 2025
d53e3f6
changed spacing to spacingFunction
fabrice-akamai Sep 26, 2025
ef5485f
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 2, 2026
2a8188d
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 3, 2026
2db02f3
Merge branch 'develop' of https://github.com/fabrice-akamai/manager i…
fabrice-akamai Mar 10, 2026
64b2563
Implement Owned groups landing content
fabrice-akamai Mar 17, 2026
57488f1
Add pendo Ids
fabrice-akamai Mar 18, 2026
2749983
Added changeset: Implement owned groups landing page content
fabrice-akamai Mar 18, 2026
df792bf
Update test cases
fabrice-akamai Mar 18, 2026
4bf9aae
Remove sortableProps from unsupported keys in owned groups table
fabrice-akamai Mar 18, 2026
bab8579
More pendo Ids
fabrice-akamai Mar 18, 2026
b19fe0a
Merge branch 'develop' into UIE-9402-owned-groups-landing-page
fabrice-akamai Mar 18, 2026
766b75a
Remove hard-coded value for share group type
fabrice-akamai Mar 19, 2026
fdef94a
Merge branch 'UIE-9402-owned-groups-landing-page' of https://github.c…
fabrice-akamai Mar 19, 2026
2d773d8
Use contextQueries with shareGroupQueries
fabrice-akamai Mar 19, 2026
ff24e6b
Add dash to updated cell when empty
fabrice-akamai Mar 20, 2026
69e4266
Replace table component with cds table
fabrice-akamai Mar 20, 2026
d649b6d
Merge branch 'develop' into UIE-9402-owned-groups-landing-page
fabrice-akamai Mar 23, 2026
37d7fe4
Rename interface name to be more generic
fabrice-akamai Mar 23, 2026
8ef0c27
Update pendo IDs in the share group action menu
fabrice-akamai Mar 23, 2026
30c3810
Improve table responsiveness and fix empty error display issue
fabrice-akamai Mar 23, 2026
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
2 changes: 2 additions & 0 deletions packages/api-v4/src/images/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './images';

export * from './sharegroups';

export * from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Implement owned groups landing page content ([#13506](https://github.com/linode/manager/pull/13506))
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ export const ImagesLandingV2 = () => {
{
title: 'Image Library',
to: '/images/image-library',
pendoId: 'Images-Library',
},
{
title: 'Share Groups',
to: '/images/share-groups',
chip: <BetaChip />,
pendoId: 'Images-Groups',
},
]);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';

import { ActionMenu } from 'src/components/ActionMenu/ActionMenu';

import type { Action } from 'src/components/ActionMenu/ActionMenu';

interface Props {
deleteButtonDisabled: boolean;
}
export const ShareGroupActionMenu = (props: Props) => {
const { deleteButtonDisabled } = props;
/* TODO: Implement action menu logic for each corresponding action */

Check warning on line 12 in packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupActionMenu.tsx

View workflow job for this annotation

GitHub Actions / ESLint Review (manager)

[eslint] reported by reviewdog 🐢 Complete the task associated to this "TODO" comment. Raw Output: {"ruleId":"sonarjs/todo-tag","severity":1,"message":"Complete the task associated to this \"TODO\" comment.","line":12,"column":6,"nodeType":null,"messageId":"completeTODO","endLine":12,"endColumn":10}
const actions: Action[] = [
{
title: 'Edit Group Details',
onClick: () => {},
disabled: false,
hidden: false,
pendoId: 'Images Groups Owned-Edit Group Details',
},
{
title: 'Add Images',
onClick: () => {},
disabled: false,
hidden: false,
pendoId: 'Images Groups Owned-Add Images',
},
{
title: 'Add Members',
onClick: () => {},
disabled: false,
hidden: false,
pendoId: 'Images Groups Owned-Add Members',
},
{
title: 'Delete',
onClick: () => {},
disabled: deleteButtonDisabled,
hidden: false,
tooltip: deleteButtonDisabled
? 'Before deleting this share group, revoke access for all members first.'
: undefined,
pendoId: 'Images Groups Owned-Delete',
},
];

return (
<ActionMenu
actionsList={actions}
ariaLabel="Action menu for share group"
data-pendo-id="Images Groups Owned-Group Action menu"
/>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatting & mobile support will need to be improved here:

  • Updated cell dash seems right-aligned
  • Updated cell contents get hidden at some point but the column remains (and the Created content gets shifted there)
  • Column names wrap or get obscured
  • Group names get obscured at a point
Screen.Recording.2026-03-23.at.11.29.39.AM.mov

This can be handled in a follow-up PR

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useProfile } from '@linode/queries';
import { Hidden, LinkButton } from '@linode/ui';
import { TableCell, TableRow } from 'akamai-cds-react-components/Table';
import React from 'react';

import { formatDate } from 'src/utilities/formatDate';

import { ShareGroupActionMenu } from './ShareGroupActionMenu';
import { StyledActionMenuWrapper } from './ShareGroupTable.styles';

import type { Sharegroup } from '@linode/api-v4';

interface Props {
shareGroup: Sharegroup;
}

export const ShareGroupRow = (props: Props) => {
const { shareGroup } = props;
const { data: profile } = useProfile();

const {
created,
description,
images_count,
label,
members_count,
updated,
id,
} = shareGroup;

return (
<TableRow data-qa-sharegroup-row={id} key={id} rowborder>
<TableCell data-pendo-id={`Images Groups Owned-Group name`}>
<LinkButton onClick={() => {}}>{label}</LinkButton>
</TableCell>
<TableCell>{description}</TableCell>
<TableCell>{members_count}</TableCell>
<Hidden smDown>
<TableCell>{images_count}</TableCell>
</Hidden>
<Hidden lgDown>
<TableCell style={{ whiteSpace: 'nowrap' }}>
{created &&
formatDate(created, {
timezone: profile?.timezone,
})}
</TableCell>
</Hidden>
<Hidden lgDown>
<TableCell style={{ whiteSpace: 'nowrap' }}>
{updated !== null
? formatDate(updated, { timezone: profile?.timezone })
: '–'}
</TableCell>
</Hidden>
<StyledActionMenuWrapper>
<ShareGroupActionMenu deleteButtonDisabled={!!members_count} />
</StyledActionMenuWrapper>
</TableRow>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { styled } from '@mui/material/styles';
import { TableCell } from 'akamai-cds-react-components/Table';

export const StyledActionMenuWrapper = styled(TableCell, {
label: 'StyledActionMenuWrapper',
})(({ theme }) => ({
justifyContent: 'flex-end',
display: 'flex',
alignItems: 'center',
maxWidth: 40,
'& button': {
padding: 0,
color: theme.tokens.alias.Content.Icon.Primary.Default,
backgroundColor: 'transparent',
},
'& button:hover': {
backgroundColor: 'transparent',
color: theme.tokens.alias.Content.Icon.Primary.Hover,
},
}));

Check warning on line 20 in packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupTable.styles.ts

View workflow job for this annotation

GitHub Actions / ESLint Review (manager)

[eslint] reported by reviewdog 🐢 Insert `⏎` Raw Output: {"ruleId":"prettier/prettier","severity":1,"message":"Insert `⏎`","line":20,"column":5,"nodeType":null,"messageId":"insert","endLine":20,"endColumn":5,"fix":{"range":[574,574],"text":"
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Tabs } from 'src/components/Tabs/Tabs';
import { getSubTabIndex } from 'src/features/Images/utils';

import { shareGroupsSubTabs as subTabs } from './shareGroupsTabsConfig';
import { ShareGroupsView } from './ShareGroupsView';

export const ShareGroupsTabs = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -39,7 +40,7 @@ export const ShareGroupsTabs = () => {
<Tabs index={subTabIndex} onChange={onTabChange}>
<TabList>
{subTabs.map((tab) => (
<Tab key={`images-${tab.type}`}>
<Tab data-pendo-id={tab.pendoId} key={`images-${tab.type}`}>
{tab.title} {tab.isBeta ? <BetaChip /> : null}
</Tab>
))}
Expand All @@ -49,7 +50,7 @@ export const ShareGroupsTabs = () => {
{subTabs.map((tab, index) => (
<SafeTabPanel index={index} key={`images-${tab.type}-content`}>
{tab.type === 'owned-groups' && (
<Notice variant="info">Owned Groups is coming soon...</Notice>
<ShareGroupsView type={'owned-groups'} />
)}
{tab.type === 'joined-groups' && (
<Notice variant="info">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { Box, Button, Hidden, Typography, useTheme } from '@linode/ui';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from 'akamai-cds-react-components/Table';
import React from 'react';

import { DocsLink } from 'src/components/DocsLink/DocsLink';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
import { TableRowError } from 'src/components/TableRowError/TableRowError';

import {
StyledImageContainer,
StyledImageTableContainer,
StyledImageTableHeader,
StyledImageTableSubheader,
} from '../ImageLibrary/ImagesTable.styles';
import { ShareGroupRow } from './ShareGroupRow';

import type { ShareGroupsViewTableColConfig } from './shareGroupsTabsConfig';
import type { APIError, Sharegroup } from '@linode/api-v4';
import type { Order } from 'src/hooks/useOrderV2';
interface HeaderProps {
buttonProps?: {
buttonText?: string;
disabled?: boolean;
onButtonClick: () => void;
tooltipText?: string;
};
description?: React.ReactNode;
docsLink?: { href: string; label?: string };
title: string;
}

interface ShareGroupsTableProps {
columns: ShareGroupsViewTableColConfig[];
emptyMessage: {
instruction?: string;
main: string;
};
error?: APIError[] | null;
eventCategory: string;
handleOrderChange: (newOrderBy: string, newOrder: Order) => void;
headerProps?: HeaderProps;
order: Order;
orderBy: string;
pagination: {
count: number;
handlePageChange: (newPage: number) => void;
handlePageSizeChange: (newSize: number) => void;
page: number;
pageSize: number;
};
query?: string;
shareGroups: Sharegroup[];
}

export const ShareGroupsTable = (props: ShareGroupsTableProps) => {
const {
columns,
headerProps,
eventCategory,
shareGroups,
query,
handleOrderChange,
error,
emptyMessage,
order,
orderBy,
pagination,
} = props;
const theme = useTheme();
return (
<StyledImageContainer>
{headerProps && headerProps.title && (
<StyledImageTableHeader>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Typography variant="h3">{headerProps.title}</Typography>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
minHeight: 40,
}}
>
{headerProps.docsLink && (
<DocsLink
analyticsLabel={headerProps.title}
data-pendo-id={`Images Groups Owned-Docs Link`}
href={headerProps.docsLink.href}
label={headerProps.docsLink.label}
/>
)}
{headerProps.buttonProps && (
<Button
buttonType="primary"
data-pendo-id={`Images Groups Owned-Create Button`}
disabled={headerProps.buttonProps.disabled}
onClick={headerProps.buttonProps.onButtonClick}
tooltipText={headerProps.buttonProps.tooltipText}
>
{headerProps.buttonProps.buttonText}
</Button>
)}
</Box>
</Box>
{headerProps.description && (
<StyledImageTableSubheader>
{headerProps.description}
</StyledImageTableSubheader>
)}
</StyledImageTableHeader>
)}
<StyledImageTableContainer>
<Table>
<TableHead>
<TableRow
headerbackground={
theme.tokens.component.Table.HeaderNested.Background
}
headerborder
>
{columns.map((col, idx) => {
const cell = col.sortableProps ? (
<TableHeaderCell
key={idx}
sort={() =>
handleOrderChange(
col.sortableProps?.label ?? col.name,
order === 'asc' ? 'desc' : 'asc'
)
}
sortable
sorted={
orderBy === col.sortableProps?.label ? order : undefined
}
style={{ ...col.style }}
>
{col.name}
</TableHeaderCell>
) : (
<TableHeaderCell key={idx} style={{ ...col.style }}>
{col.name}
</TableHeaderCell>
);

return col.hidden ? (
<Hidden key={idx} {...{ [col.hidden]: true }}>
{cell}
</Hidden>
) : (
cell
);
})}
<TableHeaderCell style={{ maxWidth: '40px' }} />
</TableRow>
</TableHead>
<TableBody>
{!error && shareGroups.length === 0 && (
<TableRow>
<TableCell
style={{
display: 'flex',
justifyContent: 'center',
}}
>
{emptyMessage.main}
</TableCell>
</TableRow>
)}
{error && query && (
<TableRowError
colSpan={columns.length + 1}
message={error[0].reason}
/>
)}

{shareGroups.map((sharegroup) => (
<ShareGroupRow key={sharegroup.id} shareGroup={sharegroup} />
))}
</TableBody>
</Table>
<PaginationFooter
count={pagination.count}
eventCategory={eventCategory}
handlePageChange={pagination.handlePageChange}
handleSizeChange={pagination.handlePageSizeChange}
page={pagination.page}
pageSize={pagination.pageSize}
/>
</StyledImageTableContainer>
</StyledImageContainer>
);
};
Loading
Loading