feat: [AH-518]: fix alignment issue of overview cards on version details page (#2828)

* feat: [AH-518]: resolve PR comments
* feat: [AH-518]: fix alignment issue of overview cards on version details page
* feat: [AH-518]: disable security scan section and show aquatrivy selected by default
* feat: [AH-518]: use normal search input instead of AI search input on global artifact list page
* feat: [AH-518]: update text to total downloads instead of total weekly downloads
* feat: [AH-518]: remove labels from UI
* feat: [AH-518]: disable cleanup policy
* feat: [AH-518]: set min width for name columns in all tables in HAR module
pull/3576/head
Shivanand Sonnad 2024-10-17 09:24:22 +00:00 committed by Harness
parent f7411b41c9
commit 465275b549
29 changed files with 103 additions and 92 deletions

View File

@ -18,3 +18,7 @@
--page-header-height: 0px;
background-color: var(--primary-bg) !important;
}
:root {
--har-table-name-column-min-width: 250px;
}

View File

@ -20,10 +20,10 @@ import type { FormikProps } from 'formik'
import { FontVariation } from '@harnessio/design-system'
import { Button, Card, Layout } from '@harnessio/uicore'
import { useStrings } from '@ar/frameworks/strings'
import CleanupPolicy from './CleanupPolicy'
import css from './CleanupPolicyList.module.scss'
// TODO: update type once BE support cleanup policy
interface CleanupPolicyListProps<T> {
name: string
formikProps: FormikProps<T>
@ -35,11 +35,11 @@ interface CleanupPolicyListProps<T> {
}
export default function CleanupPolicyList<T>(props: CleanupPolicyListProps<T>): JSX.Element {
const { name, formikProps, disabled, onAdd, addButtonLabel, getDefaultValue, onRemove } = props
const { getString } = useStrings()
const values = get(formikProps.values, name)
return (
<Layout.Vertical spacing="small">
{/* TODO: update type once BE support cleanup policy */}
{values?.map((each: any, index: number) => (
<CleanupPolicy key={each.id} disabled={disabled} name={`${name}[${index}]`} onRemove={() => onRemove(index)} />
))}
@ -54,6 +54,7 @@ export default function CleanupPolicyList<T>(props: CleanupPolicyListProps<T>):
data-testid="add-patter"
onClick={() => onAdd(getDefaultValue())}
text={addButtonLabel}
tooltip={getString('comingSoon')}
disabled={disabled}
/>
</Card>

View File

@ -15,21 +15,17 @@
*/
import React, { useContext } from 'react'
import { defaultTo } from 'lodash-es'
import { Expander } from '@blueprintjs/core'
import { Button, ButtonVariation, Layout, getErrorInfoFromErrorObject, useToaster } from '@harnessio/uicore'
import { useUpdateArtifactLabelsMutation, type ArtifactSummary } from '@harnessio/react-har-service-client'
import { Button, ButtonVariation, Layout } from '@harnessio/uicore'
import type { ArtifactSummary } from '@harnessio/react-har-service-client'
import { useDecodedParams, useGetSpaceRef } from '@ar/hooks'
import { encodeRef } from '@ar/hooks/useGetSpaceRef'
import { useDecodedParams } from '@ar/hooks'
import { useStrings } from '@ar/frameworks/strings/String'
import type { RepositoryPackageType } from '@ar/common/types'
import type { ArtifactDetailsPathParams } from '@ar/routes/types'
import WeeklyDownloads from '@ar/components/PageTitle/WeeklyDownloads'
import CreatedAndModifiedAt from '@ar/components/PageTitle/CreatedAndModifiedAt'
import ArtifactTags from '@ar/components/PageTitle/ArtifactTags'
import NameAndDescription from '@ar/components/PageTitle/NameAndDescription'
import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
import { useSetupClientModal } from '@ar/pages/repository-details/hooks/useSetupClientModal/useSetupClientModal'
import RepositoryIcon from '@ar/frameworks/RepositoryStep/RepositoryIcon'
@ -44,33 +40,11 @@ interface ArtifactDetailsHeaderContentProps {
function ArtifactDetailsHeaderContent(props: ArtifactDetailsHeaderContentProps): JSX.Element {
const { iconSize = 40 } = props
const { data } = useContext(ArtifactProviderContext)
const spaceRef = useGetSpaceRef()
const { getString } = useStrings()
const { showSuccess, showError, clear } = useToaster()
const pathParams = useDecodedParams<ArtifactDetailsPathParams>()
const { repositoryIdentifier, artifactIdentifier } = pathParams
const { packageType, imageName, modifiedAt, createdAt, downloadsCount, labels } = data as ArtifactSummary
const { mutateAsync: modifyArtifactLabels } = useUpdateArtifactLabelsMutation()
const handleUpdateArtifactLabels = async (newLabels: string[]) => {
try {
const response = await modifyArtifactLabels({
body: { labels: newLabels },
registry_ref: spaceRef,
artifact: encodeRef(artifactIdentifier)
})
if (response.content.status === 'SUCCESS') {
clear()
showSuccess(getString('artifactDetails.labelsUpdated'))
}
return true
} catch (e: any) {
showError(getErrorInfoFromErrorObject(e, true))
return false
}
}
const { packageType, imageName, modifiedAt, createdAt, downloadsCount } = data as ArtifactSummary
const [showSetupClientModal] = useSetupClientModal({
repoKey: repositoryIdentifier,
@ -87,7 +61,7 @@ function ArtifactDetailsHeaderContent(props: ArtifactDetailsHeaderContentProps):
spacing="small"
flex={{ justifyContent: 'space-between', alignItems: 'flex-start' }}>
<NameAndDescription name={imageName} hideDescription />
<WeeklyDownloads downloads={downloadsCount} label={getString('artifactDetails.downloadsThisWeek')} />
<WeeklyDownloads downloads={downloadsCount} label={getString('artifactDetails.totalDownloads')} />
</Layout.Vertical>
<Expander />
<Layout.Vertical
@ -105,18 +79,6 @@ function ArtifactDetailsHeaderContent(props: ArtifactDetailsHeaderContentProps):
icon="setting"
/>
</Layout.Horizontal>
<ArtifactTags
onChange={handleUpdateArtifactLabels}
labels={defaultTo(labels, [])}
placeholder={getString('artifactDetails.artifactLabelInputPlaceholder')}
permission={{
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY,
resource: {
resourceType: ResourceType.ARTIFACT_REGISTRY,
resourceIdentifier: repositoryIdentifier
}
}}
/>
</Layout.Vertical>
</Layout.Horizontal>
</Layout.Vertical>

View File

@ -2,3 +2,4 @@ page: Artifact Details Page
downloadsThisWeek: Downloads This Week
artifactLabelInputPlaceholder: Type input and hit enter
labelsUpdated: Artifact labels updated successfully!
totalDownloads: Total Downloads

View File

@ -14,10 +14,18 @@
* limitations under the License.
*/
import React, { useMemo } from 'react'
import React, { useMemo, useRef } from 'react'
import classNames from 'classnames'
import { flushSync } from 'react-dom'
import { Expander } from '@blueprintjs/core'
import { HarnessDocTooltip, Page, Button, ButtonVariation } from '@harnessio/uicore'
import {
HarnessDocTooltip,
Page,
Button,
ButtonVariation,
ExpandingSearchInput,
ExpandingSearchInputHandle
} from '@harnessio/uicore'
import {
GetAllHarnessArtifactsQueryQueryParams,
useGetAllHarnessArtifactsQuery
@ -30,10 +38,8 @@ import { useGetSpaceRef, useParentComponents, useParentHooks } from '@ar/hooks'
import PackageTypeSelector from '@ar/components/PackageTypeSelector/PackageTypeSelector'
import { ArtifactListVersionFilter } from './constants'
import LabelsSelector from './components/LabelsSelector/LabelsSelector'
import ArtifactListTable from './components/ArtifactListTable/ArtifactListTable'
import RepositorySelector from './components/RepositorySelector/RepositorySelector'
import ArtifactSearchInput from './components/ArtifactSearchInput/ArtifactSearchInput'
import { useArtifactListQueryParamOptions, type ArtifactListPageQueryParams } from './utils'
import css from './ArtifactListPage.module.scss'
@ -47,6 +53,7 @@ function ArtifactListPage(): JSX.Element {
const { searchTerm, isDeployedArtifacts, repositoryKey, page, size, latestVersion, packageTypes, labels } =
queryParams
const spaceRef = useGetSpaceRef('')
const searchRef = useRef({} as ExpandingSearchInputHandle)
const { preference: sortingPreference, setPreference: setSortingPreference } = usePreferenceStore<string | undefined>(
PreferenceScope.USER,
@ -84,6 +91,7 @@ function ArtifactListPage(): JSX.Element {
})
const handleClearAllFilters = (): void => {
flushSync(searchRef.current.clear)
updateQueryParams({
page: 0,
searchTerm: '',
@ -108,12 +116,23 @@ function ArtifactListPage(): JSX.Element {
/>
<Page.SubHeader className={css.subHeader}>
<div className={css.subHeaderItems}>
<ArtifactSearchInput
{/* TODO: remove AI serach input as not implemented from BE and use normal search input */}
{/* <ArtifactSearchInput
searchTerm={searchTerm || ''}
onChange={text => {
updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX })
}}
placeholder={getString('search')}
/> */}
<ExpandingSearchInput
alwaysExpanded
width={400}
placeholder={getString('search')}
onChange={text => {
updateQueryParams({ searchTerm: text || undefined, page: DEFAULT_PAGE_INDEX })
}}
defaultValue={searchTerm}
ref={searchRef}
/>
<RepositorySelector
value={repositoryKey}
@ -127,12 +146,13 @@ function ArtifactListPage(): JSX.Element {
updateQueryParams({ packageTypes: val, page: DEFAULT_PAGE_INDEX })
}}
/>
<LabelsSelector
{/* TODO: remove for beta release. but support in future */}
{/* <LabelsSelector
value={labels}
onChange={val => {
updateQueryParams({ labels: val, page: DEFAULT_PAGE_INDEX })
}}
/>
/> */}
<Expander />
<ButtonTabs
className={css.filterTabContainer}

View File

@ -24,7 +24,6 @@ import { useGetAllArtifactsByRegistryQuery } from '@harnessio/react-har-service-
import { useStrings } from '@ar/frameworks/strings'
import { DEFAULT_PAGE_INDEX, PreferenceScope } from '@ar/constants'
import { useGetSpaceRef, useParentHooks } from '@ar/hooks'
import LabelsSelector from './components/LabelsSelector/LabelsSelector'
import {
useRegistryArtifactListQueryParamOptions,
type RegistryArtifactListPageQueryParams
@ -104,12 +103,13 @@ function RegistryArtifactListPage({ pageBodyClassName }: RegistryArtifactListPag
<>
<Page.SubHeader className={css.subHeader}>
<div className={css.subHeaderItems}>
<LabelsSelector
{/* TODO: remove for beta release. but support in future */}
{/* <LabelsSelector
value={labels}
onChange={val => {
updateQueryParams({ labels: val, page: DEFAULT_PAGE_INDEX })
}}
/>
/> */}
<Expander />
<ExpandingSearchInput
alwaysExpanded

View File

@ -34,7 +34,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 1fr 10rem 10rem 15rem 15rem 15rem 50px;
grid-template-columns: minmax(var(--har-table-name-column-min-width), 1fr) 10rem 10rem 15rem 15rem 15rem 50px;
}
}

View File

@ -34,7 +34,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 1fr 15rem 20rem 15rem;
grid-template-columns: minmax(var(--har-table-name-column-min-width), 1fr) 15rem 20rem 15rem;
}
}

View File

@ -34,7 +34,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 1fr 10rem 10rem 10rem 20rem 10rem 50px !important;
grid-template-columns: minmax(var(--har-table-name-column-min-width), 1fr) 10rem 10rem 10rem 20rem 10rem 50px !important;
}
div[class*='TableV2--header'] {

View File

@ -48,7 +48,7 @@ export class DockerRepositoryType extends RepositoryStep<VirtualRegistryRequest>
protected packageType = RepositoryPackageType.DOCKER
protected repositoryName = 'Docker Repository'
protected repositoryIcon: IconName = 'docker-step'
protected supportedScanners = [Scanners.AQUA_TRIVY, Scanners.GRYPE]
protected supportedScanners = [Scanners.AQUA_TRIVY]
protected defaultValues: VirtualRegistryRequest = {
packageType: RepositoryPackageType.DOCKER,

View File

@ -95,7 +95,7 @@ function RepositoryConfigurationFormContent(
<RepositoryIncludeExcludePatternFormContent isEdit disabled={readonly} />
<Separator />
<Container className={css.upstreamProxiesContainer}>
<RepositoryCleanupPoliciesFormContent isEdit disabled={readonly} />
<RepositoryCleanupPoliciesFormContent isEdit disabled />
</Container>
</>
)}

View File

@ -36,7 +36,7 @@ interface SelectContainerScannersFormSectionProps {
export default function SelectContainerScannersFormSection(
props: SelectContainerScannersFormSectionProps
): JSX.Element {
const { packageType, readonly } = props
const { packageType } = props
const { getString } = useStrings()
const availableScannerOptions = useMemo(() => {
@ -61,7 +61,7 @@ export default function SelectContainerScannersFormSection(
packageType={packageType as RepositoryPackageType}
options={availableScannerOptions}
isEdit
readonly={readonly}
readonly
/>
</Card>
</Container>

View File

@ -20,7 +20,7 @@ import { FontVariation } from '@harnessio/design-system'
import { Checkbox, CheckboxVariant, Container, Layout, Text } from '@harnessio/uicore'
import { useLicenseStore } from '@ar/hooks'
import type { RepositoryPackageType } from '@ar/common/types'
import { Scanners, type RepositoryPackageType } from '@ar/common/types'
import { LICENSE_STATE_VALUES } from '@ar/common/LicenseTypes'
import type { VirtualRegistryRequest } from '@ar/pages/repository-details/types'
import type { ScannerConfigSpec } from '@ar/pages/repository-details/constants'
@ -55,7 +55,8 @@ export default function SelectScannerFormSection(props: SelectScannerFormSection
}
const getCheckboxState = (value: ScannerConfigSpec['value']) => {
return values.scanners?.some(each => each.name === value) || false
return value === Scanners.AQUA_TRIVY
// return values.scanners?.some(each => each.name === value) || false
}
return (

View File

@ -29,7 +29,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 1fr 12rem 10rem 10rem 10rem 10rem 12rem 50px;
grid-template-columns: minmax(var(--har-table-name-column-min-width), 1fr) 10rem 8rem 9rem 10rem 10rem 10rem 50px;
}
}

View File

@ -87,7 +87,7 @@ export default function UpstreamProxyConfigurationFormContent(
<UpstreamProxyIncludeExcludePatternFormContent formikProps={formikProps} isEdit readonly={readonly} />
<Separator />
<Container className={css.cleanupPoliciesContainer}>
<UpstreamProxyCleanupPoliciesFormContent formikProps={formikProps} isEdit disabled={readonly} />
<UpstreamProxyCleanupPoliciesFormContent formikProps={formikProps} isEdit disabled />
</Container>
</Card>
</CollapseContainer>

View File

@ -24,6 +24,10 @@
padding: var(--spacing-7) !important;
background-color: var(--white);
margin: var(--spacing-large);
& .card {
width: 100%;
}
}
.gridContainer {

View File

@ -16,6 +16,7 @@
/* eslint-disable */
// This is an auto-generated file
export declare const card: string
export declare const cardContainer: string
export declare const deploymentTablePageBody: string
export declare const gridContainer: string

View File

@ -69,7 +69,7 @@ export default function DockerVersionOverviewContent(): JSX.Element {
{response && (
<Layout.Vertical className={css.cardContainer} spacing="medium" flex={{ alignItems: 'flex-start' }}>
<DockerVersionOverviewCards />
<Card title="General Information">
<Card title={getString('versionDetails.overview.generalInformation.title')} className={css.card}>
<Layout.Vertical spacing="medium">
<Text font={{ variation: FontVariation.CARD_TITLE }}>
{getString('versionDetails.overview.generalInformation.title')}

View File

@ -63,6 +63,7 @@ export default function DeploymentOverviewCards(props: DeploymentOverviewCardsPr
<DeploymentsCard
prodCount={defaultTo(deploymentStats?.Production, 0)}
nonProdCount={defaultTo(deploymentStats?.PreProduction, 0)}
hideBuildDetails
/>
</Layout.Horizontal>
)

View File

@ -28,6 +28,6 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 1fr 10rem 20rem 15rem 15rem;
grid-template-columns: minmax(var(--har-table-name-column-min-width), 1fr) 10rem 20rem 15rem 15rem;
}
}

View File

@ -30,4 +30,8 @@
border: 1px solid var(--primary-2);
box-shadow: 0px 4px 8px 0px rgba(69, 71, 101, 0.16), 0px 0px 2px 0px rgba(40, 41, 61, 0.04);
}
&.deploymentsCard {
width: 40%;
}
}

View File

@ -18,4 +18,5 @@
// This is an auto-generated file
export declare const card: string
export declare const container: string
export declare const deploymentsCard: string
export declare const securityTestsCard: string

View File

@ -83,7 +83,7 @@ export default function DockerVersionOverviewCards() {
{responseData && (
<Layout.Horizontal width="100%" spacing="medium">
<DeploymentsCard
className={css.card}
className={classNames(css.card, css.deploymentsCard)}
onClick={() => {
handleRedirectToTab(VersionDetailsTab.DEPLOYMENTS)
}}

View File

@ -35,10 +35,11 @@ interface DeploymentsCardProps {
executionId?: string
className?: string
onClick?: () => void
hideBuildDetails?: boolean
}
export default function DeploymentsCard(props: DeploymentsCardProps) {
const { prodCount, nonProdCount, pipelineName, executionId, onClick, className, pipelineId } = props
const { prodCount, nonProdCount, pipelineName, executionId, onClick, className, pipelineId, hideBuildDetails } = props
const totalCount = defaultTo(prodCount, 0) + defaultTo(nonProdCount, 0)
const { getString } = useStrings()
const { scope } = useAppStore()
@ -72,30 +73,36 @@ export default function DeploymentsCard(props: DeploymentsCardProps) {
/>
</Layout.Horizontal>
</Layout.Vertical>
{getRouteToPipelineExecutionView && pipelineName && executionId && pipelineId && (
{!hideBuildDetails && (
<Layout.Vertical spacing="medium" flex={{ justifyContent: 'space-between', alignItems: 'flex-start' }}>
<Text font={{ variation: FontVariation.CARD_TITLE }}>
{getString('versionDetails.cards.deploymentsCard.buildTitle')}
</Text>
<Layout.Vertical spacing="xsmall">
<Link
to={getRouteToPipelineExecutionView({
accountId: scope.accountId,
orgIdentifier: scope.orgIdentifier,
projectIdentifier: scope.projectIdentifier,
pipelineIdentifier: pipelineId,
executionIdentifier: executionId,
module: 'ci'
})}
onClick={evt => {
evt.stopPropagation()
}}>
{pipelineName}
</Link>
{getRouteToPipelineExecutionView && pipelineName && executionId && pipelineId ? (
<Layout.Vertical spacing="xsmall">
<Link
to={getRouteToPipelineExecutionView({
accountId: scope.accountId,
orgIdentifier: scope.orgIdentifier,
projectIdentifier: scope.projectIdentifier,
pipelineIdentifier: pipelineId,
executionIdentifier: executionId,
module: 'ci'
})}
onClick={evt => {
evt.stopPropagation()
}}>
{pipelineName}
</Link>
<Text color={Color.GREY_500} font={{ variation: FontVariation.SMALL }}>
{getString('versionDetails.cards.deploymentsCard.executionId')}: {executionId}
</Text>
</Layout.Vertical>
) : (
<Text color={Color.GREY_500} font={{ variation: FontVariation.SMALL }}>
{getString('versionDetails.cards.deploymentsCard.executionId')}: {executionId}
{getString('na')}
</Text>
</Layout.Vertical>
)}
</Layout.Vertical>
)}
</Layout.Horizontal>

View File

@ -70,6 +70,7 @@ export default function SupplyChainCard(props: SupplyChainCardProps) {
rightIcon="download-manifests"
variation={ButtonVariation.LINK}
loading={loading}
disabled={!provenanceId}
onClick={evt => {
killEvent(evt)
download(provenanceId)

View File

@ -44,7 +44,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 40px 1fr 10rem 10rem 38rem;
grid-template-columns: 40px minmax(var(--har-table-name-column-min-width), 1fr) 10rem 10rem 38rem;
}
}
@ -52,7 +52,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 40px 1fr 15rem 10rem 10rem 30rem;
grid-template-columns: 40px minmax(var(--har-table-name-column-min-width), 1fr) 15rem 10rem 10rem 30rem;
}
}

View File

@ -31,7 +31,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 1fr 8rem 8rem 10rem 35rem;
grid-template-columns: minmax(var(--har-table-name-column-min-width), 1fr) 8rem 8rem 10rem 35rem;
}
}
@ -39,7 +39,7 @@
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
grid-template-columns: 1fr 8rem 8rem 10rem 35rem;
grid-template-columns: minmax(200px, 1fr) 8rem 8rem 10rem 35rem;
}
}

View File

@ -23,6 +23,7 @@ new: NEW
harLabel: '{{ $.repositoryList.artifactRegistry.label }}'
na: N/A
cancel: Cancel
comingSoon: Coming Soon.
failedToLoadData: Failed to load data. Please try again!
noResultsFound: No results found
optionalField: '{{name}} (optional)'

View File

@ -14,6 +14,7 @@ export interface StringsMap {
'artifactDetails.downloadsThisWeek': string
'artifactDetails.labelsUpdated': string
'artifactDetails.page': string
'artifactDetails.totalDownloads': string
'artifactList.deployedArtifacts': string
'artifactList.page': string
'artifactList.pageHeading': string
@ -230,6 +231,7 @@ export interface StringsMap {
'cleanupPolicy.name': string
'cleanupPolicy.placeholder': string
clearFilters: string
comingSoon: string
copied: string
copy: string
createdAt: string