Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export class DockerVersionType extends VersionStep<ArtifactVersionSummary> {
VersionAction.DownloadCommand,
VersionAction.ViewVersionDetails,
VersionAction.Quarantine,
VersionAction.Download
VersionAction.Download,
VersionAction.AddTag
]

protected allowedActionsOnVersionDetailsPage = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export class HelmVersionType extends VersionStep<ArtifactVersionSummary> {
VersionAction.DownloadCommand,
VersionAction.ViewVersionDetails,
VersionAction.Quarantine,
VersionAction.Download
VersionAction.Download,
VersionAction.AddTag
]

protected allowedActionsOnVersionDetailsPage = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024 Harness, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use it 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.
*/

/* Fixed width for the tag input bar only when rendered inside Add Tag modal */
.addTagInputWrapper {
:global(.bp3-input) {
width: min(400px, 90vw);
min-width: min(400px, 90vw);
max-width: 100%;

box-sizing: border-box;
}
Comment on lines +17 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stylelint error for :global needs a fix.

The current selector trips selector-pseudo-class-no-unknown, so the lint step will fail unless the rule is configured to allow :global.

✅ One quick fix (local suppression)
 .addTagInputWrapper {
+  /* stylelint-disable-next-line selector-pseudo-class-no-unknown */
   :global(.bp3-input) {
     min-width: 400px;
     max-width: 400px;
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* Fixed width for the tag input bar only when rendered inside Add Tag modal */
.addTagInputWrapper {
:global(.bp3-input) {
min-width: 400px;
max-width: 400px;
}
/* Fixed width for the tag input bar only when rendered inside Add Tag modal */
.addTagInputWrapper {
/* stylelint-disable-next-line selector-pseudo-class-no-unknown */
:global(.bp3-input) {
min-width: 400px;
max-width: 400px;
}
}
🧰 Tools
🪛 Stylelint (17.3.0)

[error] 19-19: Unexpected unknown pseudo-class selector ":global" (selector-pseudo-class-no-unknown)

(selector-pseudo-class-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/ar/pages/version-details/components/VersionActions/AddTagMenuItem.module.scss`
around lines 17 - 22, The Stylelint rule selector-pseudo-class-no-unknown is
tripping on the :global(.bp3-input) selector inside the .addTagInputWrapper
block; add a local suppression comment immediately before the offending line —
e.g. place /* stylelint-disable-next-line selector-pseudo-class-no-unknown */
above :global(.bp3-input) — so the CSS module global selector is preserved while
the linter is skipped for that line.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* 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.
*/

import React, { useRef } from 'react'
import { Formik } from 'formik'
import { Button, ButtonVariation, Layout, ModalDialog, useToaster } from '@harnessio/uicore'
import { useAddOciArtifactTagsMutation } from '@harnessio/react-har-service-client'

import { useAppStore, useParentComponents } from '@ar/hooks'
import { useStrings } from '@ar/frameworks/strings'
import { queryClient } from '@ar/utils/queryClient'
import { ResourceType } from '@ar/common/permissionTypes'
import { PermissionIdentifier } from '@ar/common/permissionTypes'
import PatternInput from '@ar/components/Form/PatternInput/PatternInput'

import type { VersionActionProps } from './types'
import css from './AddTagMenuItem.module.scss'

function normalizeTagNames(value: string[] | (string | { label: string; value: string })[]): string[] {
if (!Array.isArray(value)) return []
return value
.map(item => (typeof item === 'string' ? item : item?.value ?? ''))
.map(t => t.trim())
.filter(Boolean)
}

export interface AddTagModalContentProps {
artifactKey: string
repoKey: string
versionKey: string
hideModal: () => void
onClose?: () => void
}

export function AddTagModalContent({
artifactKey,
repoKey,
versionKey,
hideModal,
onClose
}: AddTagModalContentProps): JSX.Element {
const { scope } = useAppStore()
const { getString } = useStrings()
const { showSuccess, showError } = useToaster()
const { mutateAsync: addOciArtifactTags, isLoading: loading } = useAddOciArtifactTagsMutation()
const inputWrapperRef = useRef<HTMLDivElement>(null)

const handleSubmit = async (
tagNamesRaw: string[] | (string | { label: string; value: string })[],
currentInputValue?: string
) => {
const committed = normalizeTagNames(tagNamesRaw)
const current = currentInputValue?.trim() ?? ''
const allTags = current ? [...committed, current] : committed
console.log('allTags', allTags);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove leftover console.log debug statement.

🔧 Proposed fix
-    console.log('allTags', allTags);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log('allTags', allTags);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/ar/pages/version-details/components/VersionActions/AddTagMenuItem.tsx`
at line 68, The file contains a leftover debug statement: remove the
console.log('allTags', allTags); from the AddTagMenuItem component so it no
longer prints to the console; search for the AddTagMenuItem component or the
allTags variable in AddTagMenuItem.tsx and delete that single console.log line
(or replace it with appropriate debug-level logging via existing logger if
needed).

const trimmed = normalizeTagNames(allTags)
if (trimmed.length === 0) {
showError(getString('validationMessages.entityRequired', { entity: 'Tag name' }))
return
}
try {
await addOciArtifactTags({
queryParams: {
account_identifier: typeof scope?.accountId === 'string' ? scope.accountId : '',
org_identifier: typeof scope?.orgId === 'string' ? scope.orgId : undefined,
project_identifier: typeof scope?.projectId === 'string' ? scope.projectId : undefined,
registry_identifier: repoKey
},
body: {
package: artifactKey,
version: versionKey,
tags: trimmed
}
})
showSuccess(getString('versionList.messages.addTagSuccess'))
hideModal()
onClose?.()
queryClient.invalidateQueries(['GetAllHarnessArtifacts'])
queryClient.invalidateQueries(['ListVersions'])
} catch (e) {
showError((e as Error)?.message || getString('versionList.messages.addTagFailed'))
}
}

const handleClose = () => {
hideModal()
onClose?.()
}

return (
<Formik initialValues={{ tagNames: [] as string[] }} enableReinitialize onSubmit={() => undefined}>
{formik => (
<ModalDialog
title={getString('versionList.actions.addTag')}
isOpen={true}
onClose={handleClose}
showOverlay={loading}
footer={
<Layout.Horizontal spacing="large">
<Button
variation={ButtonVariation.PRIMARY}
onClick={() => {
const currentInput =
inputWrapperRef.current?.querySelector('input')?.value?.trim() ?? ''
handleSubmit(formik.values.tagNames, currentInput)
}}
disabled={loading}>
{getString('add')}
</Button>
<Button variation={ButtonVariation.SECONDARY} onClick={handleClose} disabled={loading}>
{getString('cancel')}
</Button>
</Layout.Horizontal>
}>
<div ref={inputWrapperRef} className={css.addTagInputWrapper}>
<PatternInput
name="tagNames"
label={getString('versionList.table.columns.tags')}
placeholder={getString('versionList.actions.addTagPlaceholder')}
disabled={loading}
/>
</div>
</ModalDialog>
)}
</Formik>
)
}

export interface AddTagMenuItemProps extends VersionActionProps {
openAddTagModal?: () => void
}

export default function AddTagMenuItem(props: AddTagMenuItemProps): JSX.Element {
const { repoKey, readonly, onClose, openAddTagModal } = props
const { getString } = useStrings()
const { RbacMenuItem } = useParentComponents()

const handleClick = () => {
openAddTagModal?.()
onClose?.()
}

return (
<RbacMenuItem
icon="plus"
text={getString('versionList.actions.addTag')}
onClick={handleClick}
disabled={readonly}
permission={{
resource: {
resourceType: ResourceType.ARTIFACT_REGISTRY,
resourceIdentifier: repoKey
},
permission: PermissionIdentifier.UPLOAD_ARTIFACT
}}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ import React, { useState } from 'react'
import { get } from 'lodash-es'

import { RepositoryConfigType } from '@ar/common/types'
import { useAppStore, useBulkDownloadFile, useAllowSoftDelete, useFeatureFlags, useRoutes } from '@ar/hooks'
import {
useAppStore,
useBulkDownloadFile,
useAllowSoftDelete,
useFeatureFlags,
useRoutes,
useParentHooks
} from '@ar/hooks'
import { useStrings } from '@ar/frameworks/strings'
import ActionButton from '@ar/components/ActionButton/ActionButton'
import CopyMenuItem from '@ar/components/MenuItemTypes/CopyMenuItem'
Expand All @@ -34,6 +41,7 @@ import RemoveQurantineMenuItem from './RemoveQurantineMenuItem'
import DownloadVersionMenuItem from './DownloadVersionMenuItem'
import SoftDeleteVersionMenuItem from './SoftDeleteVersionMenuItem'
import ReEvaluateMenuItem from './ReEvaluateMenuItem'
import AddTagMenuItem, { AddTagModalContent } from './AddTagMenuItem'

export default function VersionActions({
data,
Expand All @@ -52,12 +60,31 @@ export default function VersionActions({
const routes = useRoutes()
const { isCurrentSessionPublic } = useAppStore()
const { getString } = useStrings()
const { useModalHook } = useParentHooks()
const { HAR_DEPENDENCY_FIREWALL } = useFeatureFlags()
const isBulkDownloadFileEnabled = useBulkDownloadFile()
const allowSoftDelete = useAllowSoftDelete()
const isFirewallEnabled = data.firewallMode ? data.firewallMode !== 'ALLOW' : false
const allowReEvaluate = HAR_DEPENDENCY_FIREWALL && isFirewallEnabled && repoType === RepositoryConfigType.UPSTREAM

const closeMenu = () => {
setOpen(false)
onClose?.()
}

const [showAddTagModal, hideAddTagModal] = useModalHook(
() => (
<AddTagModalContent
artifactKey={artifactKey}
repoKey={repoKey}
versionKey={versionKey}
hideModal={hideAddTagModal}
onClose={closeMenu}
/>
),
[artifactKey, repoKey, versionKey]
)

const isAllowed = (action: VersionAction): boolean => {
if (!allowedActions) return true
return allowedActions.includes(action)
Expand Down Expand Up @@ -179,6 +206,18 @@ export default function VersionActions({
}}
/>
)}
{isAllowed(VersionAction.AddTag) && (
<AddTagMenuItem
artifactKey={artifactKey}
repoKey={repoKey}
versionKey={versionKey}
data={data}
pageType={pageType}
readonly={readonly}
onClose={closeMenu}
openAddTagModal={showAddTagModal}
/>
)}
Comment on lines +209 to +220

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hide Add Tag in public sessions for consistency.

Other mutating actions are gated by isCurrentSessionPublic, but Add Tag is currently visible. This can expose an action that will fail for public users.

Suggested guard
-      {isAllowed(VersionAction.AddTag) && (
+      {!isCurrentSessionPublic && isAllowed(VersionAction.AddTag) && (
         <AddTagMenuItem
           artifactKey={artifactKey}
           repoKey={repoKey}
           versionKey={versionKey}
           data={data}
           pageType={pageType}
           readonly={readonly}
           onClose={closeMenu}
           openAddTagModal={showAddTagModal}
         />
       )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isAllowed(VersionAction.AddTag) && (
<AddTagMenuItem
artifactKey={artifactKey}
repoKey={repoKey}
versionKey={versionKey}
data={data}
pageType={pageType}
readonly={readonly}
onClose={closeMenu}
openAddTagModal={showAddTagModal}
/>
)}
{!isCurrentSessionPublic && isAllowed(VersionAction.AddTag) && (
<AddTagMenuItem
artifactKey={artifactKey}
repoKey={repoKey}
versionKey={versionKey}
data={data}
pageType={pageType}
readonly={readonly}
onClose={closeMenu}
openAddTagModal={showAddTagModal}
/>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/ar/pages/version-details/components/VersionActions/VersionActions.tsx`
around lines 214 - 225, The Add Tag action is not currently hidden for public
sessions; wrap the AddTagMenuItem render with the same public-session guard used
for other mutating actions so it is not shown to public users. Update the JSX
condition to require both isAllowed(VersionAction.AddTag) and
!isCurrentSessionPublic (or the existing helper used elsewhere) before rendering
AddTagMenuItem (keeping artifactKey, repoKey, versionKey, data, pageType,
readonly, onClose, openAddTagModal props unchanged).

</ActionButton>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ export enum VersionAction {
ViewVersionDetails = 'viewVersionDetails',
Quarantine = 'quarantine',
Download = 'download',
ReEvaluate = 'reEvaluate'
ReEvaluate = 'reEvaluate',
AddTag = 'addTag'
}
4 changes: 4 additions & 0 deletions web/src/ar/pages/version-list/strings/strings.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ table:
tags: Tags
scanStatus: Evaluation Status
actions:
addTag: Add Tag
addTagPlaceholder: Type tag name and press Enter
deleteVersion: Delete
archiveVersion: Archive
restoreVersion: Restore
quarantine: Quarantine
removeQuarantine: Remove From Quarantine
reEvaluate: Re-Evaluate
messages:
addTagSuccess: Tag added successfully
addTagFailed: Failed to add tag
reEvaluateSuccess: Submitted request for Re-Evaluation successfully!
reEvaluateFailed: Failed to submit request!
4 changes: 4 additions & 0 deletions web/src/ar/strings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,16 @@ export interface StringsMap {
'versionDetails.versionArchived': string
'versionDetails.versionDeleted': string
'versionDetails.versionRestored': string
'versionList.actions.addTag': string
'versionList.actions.addTagPlaceholder': string
'versionList.actions.archiveVersion': string
'versionList.actions.deleteVersion': string
'versionList.actions.quarantine': string
'versionList.actions.reEvaluate': string
'versionList.actions.removeQuarantine': string
'versionList.actions.restoreVersion': string
'versionList.messages.addTagFailed': string
'versionList.messages.addTagSuccess': string
'versionList.messages.reEvaluateFailed': string
'versionList.messages.reEvaluateSuccess': string
'versionList.page': string
Expand Down