diff --git a/packages/manager/.changeset/pr-13567-upcoming-features-1775674749388.md b/packages/manager/.changeset/pr-13567-upcoming-features-1775674749388.md new file mode 100644 index 00000000000..04fd0c9e1ee --- /dev/null +++ b/packages/manager/.changeset/pr-13567-upcoming-features-1775674749388.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Private Image Sharing: implement Select Images table on Share Group Create page ([#13567](https://github.com/linode/manager/pull/13567)) diff --git a/packages/manager/src/components/Breadcrumb/Breadcrumb.tsx b/packages/manager/src/components/Breadcrumb/Breadcrumb.tsx index 6f9b14cfa49..d552503e5e8 100644 --- a/packages/manager/src/components/Breadcrumb/Breadcrumb.tsx +++ b/packages/manager/src/components/Breadcrumb/Breadcrumb.tsx @@ -40,6 +40,10 @@ export interface BreadcrumbProps { * A string representation of the path of a resource. Each crumb is separated by a `/` character. */ pathname: string; + /** + * A string that can be used to set a custom Pendo ID for the breadcrumb for tracking purposes. + */ + pendoId?: string; /** * A number indicating the position of the crumb to remove. Not zero indexed. */ @@ -65,6 +69,7 @@ export const Breadcrumb = (props: BreadcrumbProps) => { labelTitle, onEditHandlers, pathname, + pendoId, removeCrumbX, sx, } = props; @@ -91,6 +96,7 @@ export const Breadcrumb = (props: BreadcrumbProps) => { theme.spacing(3) }) }} {...breadcrumbDataAttrs} + data-pendo-id={pendoId} > { onSelect, pendoIDs, queryParamsPrefix, - selectedImageId, + selectionMode, + selectedImageIds, } = props; const theme = useTheme(); @@ -256,29 +267,31 @@ export const ImageSelectTable = (props: Props) => { Replicated in - - - - Share Group - - - - + {selectionMode === 'single' && ( + + + + Share Group + + + + + )} - Size + {selectionMode === 'single' ? 'Size' : 'Original Image'} { onSelect={() => onSelect(image)} pendoIDs={pendoIDs} regions={regions ?? []} - selected={image.id === selectedImageId} + selectedImageIds={selectedImageIds} + selectionMode={selectionMode} timezone={profile?.timezone} /> ))} diff --git a/packages/manager/src/components/ImageSelect/ImageSelectTableRow.tsx b/packages/manager/src/components/ImageSelect/ImageSelectTableRow.tsx index 98dd251e9a4..5752aef2766 100644 --- a/packages/manager/src/components/ImageSelect/ImageSelectTableRow.tsx +++ b/packages/manager/src/components/ImageSelect/ImageSelectTableRow.tsx @@ -26,20 +26,31 @@ import type { } from './constants'; import type { Image, ImageRegion, Region } from '@linode/api-v4'; import type { Theme } from '@linode/ui'; +import type { IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS } from 'src/components/ImageSelect/constants'; interface Props { image: Image; - onSelect: () => void; + onSelect?: () => void; pendoIDs: | typeof IMAGE_SELECT_TABLE_LINODE_CREATE_PENDO_IDS - | typeof IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS; + | typeof IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS + | typeof IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS; regions: Region[]; - selected: boolean; + selectedImageIds: string[]; + selectionMode: 'multi' | 'single'; timezone?: string; } export const ImageSelectTableRow = (props: Props) => { - const { image, onSelect, pendoIDs, regions, selected, timezone } = props; + const { + image, + onSelect, + pendoIDs, + regions, + selectedImageIds, + timezone, + selectionMode, + } = props; const { capabilities, @@ -89,16 +100,27 @@ export const ImageSelectTableRow = (props: Props) => { ); + const selected = selectedImageIds.includes(id); return ( - + - } - label={label} - onChange={onSelect} - sx={{ gap: 2 }} - /> + {selectionMode === 'single' ? ( + } + label={label} + onChange={onSelect} + sx={{ gap: 2 }} + /> + ) : ( + label + )} {type === 'manual' && capabilities.includes('cloud-init') && ( { /> - - - {getShareGroupDisplay()} - - + {selectionMode === 'single' && ( + + + {getShareGroupDisplay()} + + + )} {getSizeDisplay()} diff --git a/packages/manager/src/components/ImageSelect/constants.ts b/packages/manager/src/components/ImageSelect/constants.ts index 705234914b8..8f0480791f6 100644 --- a/packages/manager/src/components/ImageSelect/constants.ts +++ b/packages/manager/src/components/ImageSelect/constants.ts @@ -22,3 +22,18 @@ export const IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS = { replicatedRegionPopover: 'Linodes Rebuild Images-Replicated in', shareGroupInfoIcon: 'Linodes Rebuild Images-Share Group info icon', }; + +export const IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS = { + searchImagesBar: 'Share Groups Create Images-Search click', + tagFilterSelect: 'Share Groups Create Images-Filter by Tag click', + regionFilterSelect: 'Share Groups Create Images-Filter by Region click', + metadataSupportedIcon: 'Share Groups Create Images-Metadata Supported icon', + replicatedRegionPopover: 'Share Groups Create Images-Replicated in', + shareGroupInfoIcon: 'Share Groups Create Images-Share Group info icon', + createImageLink: 'Share Groups Create Images-Create Image Link', + uploadImageLink: 'Share Groups Create Images-Upload Image Link', + imageCheckbox: 'Share Groups Create Images-Image Checkbox', + pageSizeSelect: 'Share Groups Create Images-Page Size Select', + previousPageButton: 'Share Groups Create Images-Previous Page Button', + nextPageButton: 'Share Groups Create Images-Next Page Button', +}; diff --git a/packages/manager/src/components/LandingHeader/LandingHeader.tsx b/packages/manager/src/components/LandingHeader/LandingHeader.tsx index a617543b766..04cd13525a3 100644 --- a/packages/manager/src/components/LandingHeader/LandingHeader.tsx +++ b/packages/manager/src/components/LandingHeader/LandingHeader.tsx @@ -29,6 +29,7 @@ export interface LandingHeaderProps { onButtonClick?: () => void; onButtonKeyPress?: (e: React.KeyboardEvent) => void; onDocsClick?: () => void; + pendoId?: string; removeCrumbX?: number | number[]; shouldHideDocsAndCreateButtons?: boolean; spacingBottom?: 0 | 4 | 16 | 24; @@ -46,6 +47,7 @@ export const LandingHeader = ({ breadcrumbProps, buttonDataAttrs, createButtonText, + pendoId, disabledBreadcrumbEditButton, disabledCreateButton, docsLabel, @@ -98,6 +100,7 @@ export const LandingHeader = ({ {...breadcrumbDataAttrs} {...breadcrumbProps} disabledBreadcrumbEditButton={disabledBreadcrumbEditButton} + pendoId={pendoId} /> {!shouldHideDocsAndCreateButtons && ( diff --git a/packages/manager/src/features/Images/ImagesLanding/v2/ImageLibrary/ImagesTable.styles.ts b/packages/manager/src/features/Images/ImagesLanding/v2/ImageLibrary/ImagesTable.styles.ts index 8f4eea5e6aa..c61583d2128 100644 --- a/packages/manager/src/features/Images/ImagesLanding/v2/ImageLibrary/ImagesTable.styles.ts +++ b/packages/manager/src/features/Images/ImagesLanding/v2/ImageLibrary/ImagesTable.styles.ts @@ -37,8 +37,4 @@ export const StyledImageTableContainer = styled(Box, { '& cds-table-row:last-child:not([rowborder])': { borderBottom: `1px solid ${theme.tokens.component.Table.Row.Border}`, }, - - '& cds-table-header-cell, & cds-table-cell': { - boxSizing: 'border-box', - }, })); diff --git a/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreate.tsx b/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreate.tsx index 1a0fef28bec..55bf18aecf0 100644 --- a/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreate.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreate.tsx @@ -11,19 +11,31 @@ import { } from '@linode/ui'; import { useNavigate } from '@tanstack/react-router'; import * as React from 'react'; -import { Controller, useForm } from 'react-hook-form'; +import { Controller, useController, useForm } from 'react-hook-form'; -import type { CreateSharegroupPayload } from '@linode/api-v4'; +import { IMAGE_SELECT_TABLE_SHARE_GROUP_CREATE_PENDO_IDS } from 'src/components/ImageSelect/constants'; +import { ImageSelectTable } from 'src/components/ImageSelect/ImageSelectTable'; + +import { CREATE_SHARE_GROUP_PENDO_IDS } from '../../constants'; + +import type { CreateSharegroupPayload, Image } from '@linode/api-v4'; export const ShareGroupsCreate = () => { const navigate = useNavigate(); const { mutateAsync: createShareGroup } = useCreateShareGroupMutation(); - const { control, handleSubmit, setError } = - useForm(); + const { + control, + handleSubmit, + setError, + formState: { isSubmitting }, + } = useForm(); - const selectedImages = []; + const { field: imagesController, fieldState } = useController({ + control, + name: 'images', + }); const onSubmit = handleSubmit(async (values) => { try { @@ -43,6 +55,20 @@ export const ShareGroupsCreate = () => { } } }); + + const onChange = (image: Image) => { + const selectedImages = imagesController.value ?? []; + + const { id, label, description } = image; + const imagePayload = { id, label, ...(description && { description }) }; + + if (!selectedImages.some((img) => img.id === id)) { + imagesController.onChange([...selectedImages, imagePayload]); + } else { + imagesController.onChange(selectedImages.filter((img) => img.id !== id)); + } + }; + return (
@@ -61,6 +87,7 @@ export const ShareGroupsCreate = () => { noMarginTop required {...field} + data-pendo-id={CREATE_SHARE_GROUP_PENDO_IDS.label} errorText={fieldState.error?.message} onChange={(e) => field.onChange( @@ -81,6 +108,7 @@ export const ShareGroupsCreate = () => { multiline noMarginTop {...field} + data-pendo-id={CREATE_SHARE_GROUP_PENDO_IDS.description} onChange={(e) => field.onChange( e.target.value === '' ? undefined : e.target.value @@ -95,18 +123,32 @@ export const ShareGroupsCreate = () => { Images - Images table is coming soon... + img.id) ?? [] + } + selectionMode="multi" + /> - Selected images ({selectedImages.length}) + Selected images ({imagesController.value?.length ?? 0}) Selected images is coming soon... - diff --git a/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreateContainer.tsx b/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreateContainer.tsx index 2c298112a3b..536704009c9 100644 --- a/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreateContainer.tsx +++ b/packages/manager/src/features/Images/ImagesLanding/v2/ShareGroups/ShareGroupsCreate/ShareGroupsCreateContainer.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { DocumentTitleSegment } from 'src/components/DocumentTitle/DocumentTitle'; import { LandingHeader } from 'src/components/LandingHeader/LandingHeader'; +import { CREATE_SHARE_GROUP_PENDO_IDS } from '../../constants'; import { ShareGroupsCreate } from './ShareGroupsCreate'; export const ShareGroupsCreateContainer = () => { @@ -13,6 +14,7 @@ export const ShareGroupsCreateContainer = () => { diff --git a/packages/manager/src/features/Images/ImagesLanding/v2/constants.ts b/packages/manager/src/features/Images/ImagesLanding/v2/constants.ts index 1d50213647e..7413ba56074 100644 --- a/packages/manager/src/features/Images/ImagesLanding/v2/constants.ts +++ b/packages/manager/src/features/Images/ImagesLanding/v2/constants.ts @@ -1 +1,8 @@ export const DEFAULT_PAGE_SIZES = [25, 50, 75, 100]; + +export const CREATE_SHARE_GROUP_PENDO_IDS = { + landingHeader: 'Images Share Groups Create-Landing Header', + label: 'Share Groups Create Images-Label', + description: 'Share Groups Create Images-Description', + createButton: 'Share Groups Create Images-Create Button', +}; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Tabs/Images.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Tabs/Images.tsx index e24870c6780..041d3ec676e 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Tabs/Images.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Tabs/Images.tsx @@ -101,7 +101,8 @@ export const Images = () => { errorText={fieldState.error?.message} onSelect={onChange} pendoIDs={IMAGE_SELECT_TABLE_LINODE_CREATE_PENDO_IDS} - selectedImageId={field.value} + selectedImageIds={field.value ? [field.value] : []} + selectionMode="single" /> ) : ( diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/Image.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/Image.tsx index 53b582e89c6..ed70fc7e2d9 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/Image.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/Image.tsx @@ -51,7 +51,8 @@ export const Image = (props: Props) => { onSelect={(image) => field.onChange(image?.id ?? null)} pendoIDs={IMAGE_SELECT_TABLE_LINODE_REBUILD_PENDO_IDS} queryParamsPrefix="images" - selectedImageId={field.value} + selectedImageIds={field.value ? [field.value] : []} + selectionMode="single" /> ) : (