feat: [AH-996]: API integration for NPM package type (#3632)

* feat: [AH-996]: API integration for NPM package type
* feat: [AH-996]: API integration for NPM package type
main
Shivanand Sonnad 2025-04-04 08:45:23 +00:00 committed by Harness
parent e7d7db57b4
commit f9c9766a1e
13 changed files with 124 additions and 188 deletions

View File

@ -20,7 +20,7 @@ import { defaultTo } from 'lodash-es'
import copy from 'clipboard-copy' import copy from 'clipboard-copy'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import type { TableExpandedToggleProps } from 'react-table' import type { TableExpandedToggleProps } from 'react-table'
import { Button, ButtonProps, ButtonVariation, Layout, Text } from '@harnessio/uicore' import { Button, ButtonProps, ButtonVariation, Layout, Text, TextProps } from '@harnessio/uicore'
import type { IconName, IconProps } from '@harnessio/icons' import type { IconName, IconProps } from '@harnessio/icons'
import { Color, FontVariation } from '@harnessio/design-system' import { Color, FontVariation } from '@harnessio/design-system'
@ -151,14 +151,14 @@ export const CountCell = ({ value, icon, iconProps }: CountCellProps): JSX.Eleme
) )
} }
interface TextCellProps { interface TextCellProps extends TextProps {
value: string | undefined value: string | undefined
} }
export const TextCell = ({ value }: TextCellProps): JSX.Element => { export const TextCell = ({ value, ...rest }: TextCellProps): JSX.Element => {
const { getString } = useStrings() const { getString } = useStrings()
return ( return (
<Text color={Color.GREY_900} lineClamp={1}> <Text color={Color.GREY_900} lineClamp={1} {...rest}>
{defaultTo(value, getString('na'))} {defaultTo(value, getString('na'))}
</Text> </Text>
) )

View File

@ -21,7 +21,7 @@ import { useParentHooks } from '@ar/hooks'
import { useStrings } from '@ar/frameworks/strings' import { useStrings } from '@ar/frameworks/strings'
import { ButtonTab, ButtonTabs } from '@ar/components/ButtonTabs/ButtonTabs' import { ButtonTab, ButtonTabs } from '@ar/components/ButtonTabs/ButtonTabs'
import VersionFilesProvider from '@ar/pages/version-details/context/VersionFilesProvider' import VersionFilesProvider from '@ar/pages/version-details/context/VersionFilesProvider'
import VersionDependencyListProvider from '@ar/pages/version-details/context/VersionDependencyListProvider' import VersionOverviewProvider from '@ar/pages/version-details/context/VersionOverviewProvider'
import NpmVersionFilesContent from './NpmVersionFilesContent' import NpmVersionFilesContent from './NpmVersionFilesContent'
import NpmVersionReadmeContent from './NpmVersionReadmeContent' import NpmVersionReadmeContent from './NpmVersionReadmeContent'
@ -42,6 +42,7 @@ export default function NpmVersionArtifactDetailsPage() {
) )
return ( return (
<VersionOverviewProvider>
<Layout.Vertical padding="large" spacing="large"> <Layout.Vertical padding="large" spacing="large">
<ButtonTabs small bold selectedTabId={detailsTab} onChange={handleTabChange}> <ButtonTabs small bold selectedTabId={detailsTab} onChange={handleTabChange}>
<ButtonTab <ButtonTab
@ -66,14 +67,11 @@ export default function NpmVersionArtifactDetailsPage() {
id={NpmArtifactDetailsTabEnum.Dependencies} id={NpmArtifactDetailsTabEnum.Dependencies}
icon="layers" icon="layers"
iconProps={{ size: 12 }} iconProps={{ size: 12 }}
panel={ panel={<NpmVersionDependencyContent />}
<VersionDependencyListProvider>
<NpmVersionDependencyContent />
</VersionDependencyListProvider>
}
title={getString('versionDetails.artifactDetails.tabs.dependencies')} title={getString('versionDetails.artifactDetails.tabs.dependencies')}
/> />
</ButtonTabs> </ButtonTabs>
</Layout.Vertical> </Layout.Vertical>
</VersionOverviewProvider>
) )
} }

View File

@ -14,22 +14,23 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useContext } from 'react' import React from 'react'
import { DEFAULT_PAGE_INDEX } from '@ar/constants' import { useVersionOverview } from '@ar/pages/version-details/context/VersionOverviewProvider'
import { VersionDependencyListContext } from '@ar/pages/version-details/context/VersionDependencyListProvider' import type { IDependencyList } from '@ar/pages/version-details/components/ArtifactDependencyListTable/types'
import ArtifactDependencyListTable from '@ar/pages/version-details/components/ArtifactDependencyListTable/ArtifactDependencyListTable' import ArtifactDependencyListTable from '@ar/pages/version-details/components/ArtifactDependencyListTable/ArtifactDependencyListTable'
import type { NpmVersionDetailsConfig } from '../../types'
export default function NpmVersionDependencyContent() { export default function NpmVersionDependencyContent() {
const { data, updateQueryParams, sort } = useContext(VersionDependencyListContext) const { data } = useVersionOverview<NpmVersionDetailsConfig>()
return ( const versionMetatdata = data.metadata?.versions?.[data.version]
<ArtifactDependencyListTable const dependencies = versionMetatdata?.dependencies || {}
data={data}
gotoPage={pageNumber => updateQueryParams({ page: pageNumber })} const dependencyList: IDependencyList = Object.entries(dependencies).map(([key, value]) => ({
setSortBy={sortArr => { name: key,
updateQueryParams({ sort: sortArr, page: DEFAULT_PAGE_INDEX }) version: value as string
}} }))
sortBy={sort}
/> return <ArtifactDependencyListTable data={dependencyList} />
)
} }

View File

@ -16,13 +16,21 @@
import React from 'react' import React from 'react'
import { Container } from '@harnessio/uicore' import { Container } from '@harnessio/uicore'
import { useStrings } from '@ar/frameworks/strings'
import { useVersionOverview } from '@ar/pages/version-details/context/VersionOverviewProvider'
import ReadmeFileContent from '@ar/pages/version-details/components/ReadmeFileContent/ReadmeFileContent' import ReadmeFileContent from '@ar/pages/version-details/components/ReadmeFileContent/ReadmeFileContent'
import { MOCK_README_CONTENT } from './mockData'
import type { NpmVersionDetailsConfig } from '../../types'
export default function NpmVersionReadmeContent() { export default function NpmVersionReadmeContent() {
const { data } = useVersionOverview<NpmVersionDetailsConfig>()
const versionMetatdata = data.metadata?.versions?.[data.version]
const { getString } = useStrings()
return ( return (
<Container> <Container>
<ReadmeFileContent source={MOCK_README_CONTENT} /> <ReadmeFileContent source={versionMetatdata?.readme || getString('noReadme')} />
</Container> </Container>
) )
} }

View File

@ -14,19 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useContext } from 'react' import React from 'react'
import { defaultTo } from 'lodash-es' import { defaultTo } from 'lodash-es'
import { FontVariation } from '@harnessio/design-system' import { FontVariation } from '@harnessio/design-system'
import { Card, Container, Layout, Text } from '@harnessio/uicore' import { Card, Container, Layout, Text } from '@harnessio/uicore'
import type { ArtifactDetail, NpmArtifactDetailConfig } from '@harnessio/react-har-service-client'
import { useStrings } from '@ar/frameworks/strings' import { useStrings } from '@ar/frameworks/strings'
import { DEFAULT_DATE_TIME_FORMAT } from '@ar/constants' import { DEFAULT_DATE_TIME_FORMAT } from '@ar/constants'
import { getReadableDateTime } from '@ar/common/dateUtils' import { getReadableDateTime } from '@ar/common/dateUtils'
import { LabelValueTypeEnum } from '@ar/pages/version-details/components/LabelValueContent/type' import { LabelValueTypeEnum } from '@ar/pages/version-details/components/LabelValueContent/type'
import { VersionOverviewContext } from '@ar/pages/version-details/context/VersionOverviewProvider' import { useVersionOverview } from '@ar/pages/version-details/context/VersionOverviewProvider'
import { LabelValueContent } from '@ar/pages/version-details/components/LabelValueContent/LabelValueContent' import { LabelValueContent } from '@ar/pages/version-details/components/LabelValueContent/LabelValueContent'
import type { NpmVersionDetailsConfig } from '../../types'
import css from './overview.module.scss' import css from './overview.module.scss'
interface NpmVersionGeneralInfoProps { interface NpmVersionGeneralInfoProps {
@ -35,8 +35,8 @@ interface NpmVersionGeneralInfoProps {
export default function NpmVersionGeneralInfo(props: NpmVersionGeneralInfoProps) { export default function NpmVersionGeneralInfo(props: NpmVersionGeneralInfoProps) {
const { className } = props const { className } = props
const contextValue = useContext(VersionOverviewContext) const { data } = useVersionOverview<NpmVersionDetailsConfig>()
const data = contextValue.data as ArtifactDetail & NpmArtifactDetailConfig const versionMetatdata = data.metadata?.versions?.[data.version]
const { getString } = useStrings() const { getString } = useStrings()
return ( return (
<Card <Card
@ -64,32 +64,40 @@ export default function NpmVersionGeneralInfo(props: NpmVersionGeneralInfoProps)
value={defaultTo(data.downloadCount?.toLocaleString(), 0)} value={defaultTo(data.downloadCount?.toLocaleString(), 0)}
type={LabelValueTypeEnum.Text} type={LabelValueTypeEnum.Text}
/> />
{versionMetatdata?.repository && (
<LabelValueContent <LabelValueContent
label={getString('versionDetails.overview.generalInformation.repository')} label={getString('versionDetails.overview.generalInformation.repository')}
value={data.metadata?.repository} value={versionMetatdata.repository?.url}
type={LabelValueTypeEnum.Link} type={LabelValueTypeEnum.Link}
/> />
)}
{versionMetatdata?.homepage && (
<LabelValueContent <LabelValueContent
label={getString('versionDetails.overview.generalInformation.homepage')} label={getString('versionDetails.overview.generalInformation.homepage')}
value={data.metadata?.homepage} value={versionMetatdata.homepage}
type={LabelValueTypeEnum.Link} type={LabelValueTypeEnum.Link}
/> />
)}
{versionMetatdata?.license && (
<LabelValueContent <LabelValueContent
label={getString('versionDetails.overview.generalInformation.license')} label={getString('versionDetails.overview.generalInformation.license')}
value={data.metadata?.license} value={versionMetatdata.license}
type={LabelValueTypeEnum.Text} type={LabelValueTypeEnum.Text}
/> />
)}
<LabelValueContent <LabelValueContent
label={getString('versionDetails.overview.generalInformation.uploadedBy')} label={getString('versionDetails.overview.generalInformation.uploadedBy')}
value={getReadableDateTime(Number(data.modifiedAt), DEFAULT_DATE_TIME_FORMAT)} value={getReadableDateTime(Number(data.modifiedAt), DEFAULT_DATE_TIME_FORMAT)}
type={LabelValueTypeEnum.Text} type={LabelValueTypeEnum.Text}
/> />
{data.metadata?.pullCommand && (
<LabelValueContent <LabelValueContent
label={getString('versionDetails.overview.generalInformation.pullCommand')} label={getString('versionDetails.overview.generalInformation.pullCommand')}
value={data.metadata?.pullCommand} value={data.metadata.pullCommand}
type={LabelValueTypeEnum.CommandBlock} type={LabelValueTypeEnum.CommandBlock}
/> />
)}
</Container> </Container>
</Layout.Vertical> </Layout.Vertical>
</Card> </Card>

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import type { ArtifactDetail, NpmArtifactDetailConfig } from '@harnessio/react-har-service-client'
import type { VersionDetailsQueryParams } from '../types' import type { VersionDetailsQueryParams } from '../types'
export enum NpmArtifactDetailsTabEnum { export enum NpmArtifactDetailsTabEnum {
@ -21,7 +22,10 @@ export enum NpmArtifactDetailsTabEnum {
Files = 'files', Files = 'files',
Dependencies = 'dependencies' Dependencies = 'dependencies'
} }
export interface NpmVersionDetailsQueryParams extends VersionDetailsQueryParams { export interface NpmVersionDetailsQueryParams extends VersionDetailsQueryParams {
detailsTab: NpmArtifactDetailsTabEnum detailsTab: NpmArtifactDetailsTabEnum
digest: string digest: string
} }
export type NpmVersionDetailsConfig = ArtifactDetail & NpmArtifactDetailConfig

View File

@ -14,22 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
import React, { useContext } from 'react' import React from 'react'
import { DEFAULT_PAGE_INDEX } from '@ar/constants'
import { VersionDependencyListContext } from '@ar/pages/version-details/context/VersionDependencyListProvider'
import ArtifactDependencyListTable from '@ar/pages/version-details/components/ArtifactDependencyListTable/ArtifactDependencyListTable' import ArtifactDependencyListTable from '@ar/pages/version-details/components/ArtifactDependencyListTable/ArtifactDependencyListTable'
export default function NuGetVersionDependencyContent() { export default function NuGetVersionDependencyContent() {
const { data, updateQueryParams, sort } = useContext(VersionDependencyListContext) // TODO: Implement NuGetVersionDependencyContent
return ( return <ArtifactDependencyListTable data={[]} />
<ArtifactDependencyListTable
data={data}
gotoPage={pageNumber => updateQueryParams({ page: pageNumber })}
setSortBy={sortArr => {
updateQueryParams({ sort: sortArr, page: DEFAULT_PAGE_INDEX })
}}
sortBy={sort}
/>
)
} }

View File

@ -1,35 +0,0 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.table {
--typography-size: 12px;
--typography-weight: 400;
--line-height: 20px;
padding: var(--spacing-large);
[role='cell'],
[role='columnheader'] {
width: auto !important;
padding-right: var(--spacing-large);
}
div[class*='TableV2--cells'],
div[class*='TableV2--header'] {
display: grid !important;
width: 100%; /* Ensure the container doesn't exceed 100% width */
grid-template-columns: 1fr max-content;
}
}

View File

@ -15,82 +15,40 @@
*/ */
import React from 'react' import React from 'react'
import classNames from 'classnames'
import type { Column } from 'react-table' import type { Column } from 'react-table'
import { type PaginationProps, TableV2 } from '@harnessio/uicore' import { TableV2 } from '@harnessio/uicore'
import type { FileDetail, FileDetailResponseResponse } from '@harnessio/react-har-service-client'
import { useParentHooks } from '@ar/hooks'
import { useStrings } from '@ar/frameworks/strings' import { useStrings } from '@ar/frameworks/strings'
import type { IDependencyItem, IDependencyList } from './types'
import { DependencyNameCell, DependencyVersionCell } from './ArtifactDependencyListTableCells' import { DependencyNameCell, DependencyVersionCell } from './ArtifactDependencyListTableCells'
import css from './ArtifactDependencyListTable.module.scss'
export interface DependencyListSortBy {
sort: 'name' | 'size' | 'created'
}
interface ArtifactDependencyListTableProps { interface ArtifactDependencyListTableProps {
data: FileDetailResponseResponse data: IDependencyList
gotoPage: (pageNumber: number) => void
onPageSizeChange?: PaginationProps['onPageSizeChange']
setSortBy: (sortBy: string[]) => void
sortBy: string[]
minimal?: boolean minimal?: boolean
className?: string className?: string
} }
export default function ArtifactDependencyListTable(props: ArtifactDependencyListTableProps): JSX.Element { export default function ArtifactDependencyListTable(props: ArtifactDependencyListTableProps): JSX.Element {
const { data, gotoPage, onPageSizeChange, sortBy, setSortBy, className } = props const { data, className } = props
const { useDefaultPaginationProps } = useParentHooks()
const { getString } = useStrings() const { getString } = useStrings()
const { files, itemCount = 0, pageCount = 0, pageIndex, pageSize = 0 } = data const columns: Column<IDependencyItem>[] = React.useMemo(() => {
const paginationProps = useDefaultPaginationProps({
itemCount,
pageSize,
pageCount,
pageIndex,
gotoPage,
onPageSizeChange
})
const [currentSort, currentOrder] = sortBy
const columns: Column<FileDetail>[] = React.useMemo(() => {
const getServerSortProps = (id: string) => {
return {
enableServerSort: true,
isServerSorted: currentSort === id,
isServerSortedDesc: currentOrder === 'DESC',
getSortedColumn: ({ sort }: DependencyListSortBy) => {
setSortBy([sort, currentOrder === 'DESC' ? 'ASC' : 'DESC'])
}
}
}
return [ return [
{ {
Header: getString('versionDetails.dependencyList.table.columns.name'), Header: getString('versionDetails.dependencyList.table.columns.name'),
accessor: 'name', accessor: 'name',
Cell: DependencyNameCell, Cell: DependencyNameCell,
serverSortProps: getServerSortProps('name') width: '100%'
}, },
{ {
Header: getString('versionDetails.dependencyList.table.columns.version'), Header: getString('versionDetails.dependencyList.table.columns.version'),
accessor: 'version', accessor: 'version',
Cell: DependencyVersionCell, Cell: DependencyVersionCell,
serverSortProps: getServerSortProps('version') width: '20%'
} }
].filter(Boolean) as unknown as Column<FileDetail>[] ].filter(Boolean) as unknown as Column<IDependencyItem>[]
}, [currentOrder, currentSort, getString]) }, [getString])
return ( return <TableV2 className={className} columns={columns} data={data} sortable={false} />
<TableV2
className={classNames(css.table, className)}
columns={columns}
data={files}
pagination={paginationProps}
sortable
/>
)
} }

View File

@ -15,6 +15,7 @@
*/ */
import React from 'react' import React from 'react'
import { FontVariation } from '@harnessio/design-system'
import type { FileDetail } from '@harnessio/react-har-service-client' import type { FileDetail } from '@harnessio/react-har-service-client'
import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table' import type { Cell, CellValue, ColumnInstance, Renderer, Row, TableInstance } from 'react-table'
@ -30,9 +31,9 @@ type CellTypeWithActions<D extends Record<string, any>, V = any> = TableInstance
type CellType = Renderer<CellTypeWithActions<FileDetail>> type CellType = Renderer<CellTypeWithActions<FileDetail>>
export const DependencyNameCell: CellType = ({ value }) => { export const DependencyNameCell: CellType = ({ value }) => {
return <TableCells.TextCell value={value} /> return <TableCells.TextCell font={{ variation: FontVariation.BODY }} value={value} />
} }
export const DependencyVersionCell: CellType = ({ value }) => { export const DependencyVersionCell: CellType = ({ value }) => {
return <TableCells.TextCell value={value} /> return <TableCells.TextCell font={{ variation: FontVariation.BODY }} value={value} />
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2023 Harness, Inc. * Copyright 2024 Harness, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
/* eslint-disable */ export type IDependencyItem = {
// This is an auto-generated file name: string
export declare const table: string version: string
}
export type IDependencyList = IDependencyItem[]

View File

@ -40,6 +40,7 @@ plusNewName: + New {{name}}
enterPlaceholder: Enter {{name}} enterPlaceholder: Enter {{name}}
descriptionPlaceholder: Enter Description descriptionPlaceholder: Enter Description
noDescription: No Description noDescription: No Description
noReadme: No Readme
lastUpdated: Last Updated lastUpdated: Last Updated
createdAt: Created At createdAt: Created At
modifiedAt: Modified At modifiedAt: Modified At

View File

@ -348,6 +348,7 @@ export interface StringsMap {
new: string new: string
noDescription: string noDescription: string
noMatchingFilterData: string noMatchingFilterData: string
noReadme: string
noResultsFound: string noResultsFound: string
nonProd: string nonProd: string
nonProdCount: string nonProdCount: string