Skip to content

Commit a01ec89

Browse files
committed
fix(FR-2831): disable Deploy as service action when no compatible presets exist
1 parent 024fcd9 commit a01ec89

3 files changed

Lines changed: 84 additions & 2 deletions

File tree

react/src/components/VFolderNodes.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@license
33
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
44
*/
5+
import { VFolderNodesDeploymentPresetsCheckQuery } from '../__generated__/VFolderNodesDeploymentPresetsCheckQuery.graphql';
56
import {
67
VFolderNodesFragment$data,
78
VFolderNodesFragment$key,
@@ -44,7 +45,7 @@ import dayjs from 'dayjs';
4445
import * as _ from 'lodash-es';
4546
import React, { useState } from 'react';
4647
import { useTranslation } from 'react-i18next';
47-
import { graphql, useFragment } from 'react-relay';
48+
import { graphql, useFragment, useLazyLoadQuery } from 'react-relay';
4849

4950
export const statusTagColor = {
5051
// mountable
@@ -104,6 +105,15 @@ interface VFolderNameCellProps {
104105
* additionally redirects admins (project/domain/super) to that page.
105106
*/
106107
disableProjectFolderActions?: boolean;
108+
/**
109+
* When true, the row-level "Deploy as service" action for model folders
110+
* is rendered disabled with a tooltip explaining that no compatible
111+
* deployment presets are available. Computed once at the table level
112+
* by `VFolderNodes` (via `deploymentRevisionPresets`) so we don't fire
113+
* one query per row. The `VFolderDeployModal` retains a `null`-return
114+
* fallback for the same condition as defense in depth.
115+
*/
116+
hasNoCompatiblePresets?: boolean;
107117
}
108118

109119
const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
@@ -114,6 +124,7 @@ const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
114124
onDeleteForever,
115125
onStartServiceFallback,
116126
disableProjectFolderActions = false,
127+
hasNoCompatiblePresets = false,
117128
}) => {
118129
'use memo';
119130
const { t } = useTranslation();
@@ -149,6 +160,10 @@ const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
149160
key: 'start-service',
150161
title: t('modelService.DeployAsService'),
151162
icon: <BAIEndpointsIcon />,
163+
disabled: hasNoCompatiblePresets,
164+
disabledReason: hasNoCompatiblePresets
165+
? t('data.folders.NoCompatibleDeploymentPresets')
166+
: undefined,
152167
onClick: () => onStartServiceFallback(vfolderId),
153168
}
154169
: null,
@@ -309,6 +324,28 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
309324

310325
const filteredVFolders = filterOutNullAndUndefined(vfolders);
311326

327+
// Project-scoped check: are any deployment presets available for this
328+
// project? Used to gate the row-level "Deploy as service" action so model
329+
// folders show a disabled tooltip ("No compatible deployment presets…")
330+
// instead of a fully interactive button that opens a modal which then
331+
// returns `null` (FR-2831). Run once per table mount, not once per row.
332+
// TODO(needs-backend): once `deploymentRevisionPresets` exposes a
333+
// per-vfolder compatibility filter, narrow this check to the rows that
334+
// actually have a model folder and respect their compatibility.
335+
const { deploymentRevisionPresets } =
336+
useLazyLoadQuery<VFolderNodesDeploymentPresetsCheckQuery>(
337+
graphql`
338+
query VFolderNodesDeploymentPresetsCheckQuery {
339+
deploymentRevisionPresets(first: 0) {
340+
count
341+
}
342+
}
343+
`,
344+
{},
345+
{ fetchPolicy: 'store-or-network' },
346+
);
347+
const hasNoCompatiblePresets = (deploymentRevisionPresets?.count ?? 0) === 0;
348+
312349
const deleteMutation = useTanMutation({
313350
mutationFn: (id: string) => {
314351
return baiClient.vfolder.delete_by_id(toLocalId(id));
@@ -347,6 +384,7 @@ const VFolderNodes: React.FC<VFolderNodesProps> = ({
347384
<VFolderNameCell
348385
vfolder={vfolder}
349386
disableProjectFolderActions={disableProjectFolderActions}
387+
hasNoCompatiblePresets={hasNoCompatiblePresets}
350388
onShare={() => {
351389
vfolder?.user === currentUser?.uuid
352390
? setInviteFolderId(toLocalId(vfolder?.id ?? null))

react/src/components/VFolderNodesV2.tsx

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
44
*/
55
import { VFolderNodesV2DeleteMutation } from '../__generated__/VFolderNodesV2DeleteMutation.graphql';
6+
import { VFolderNodesV2DeploymentPresetsCheckQuery } from '../__generated__/VFolderNodesV2DeploymentPresetsCheckQuery.graphql';
67
import {
78
VFolderNodesV2Fragment$data,
89
VFolderNodesV2Fragment$key,
@@ -49,7 +50,12 @@ import dayjs from 'dayjs';
4950
import * as _ from 'lodash-es';
5051
import React, { Suspense, useState } from 'react';
5152
import { useTranslation } from 'react-i18next';
52-
import { graphql, useFragment, useMutation } from 'react-relay';
53+
import {
54+
graphql,
55+
useFragment,
56+
useLazyLoadQuery,
57+
useMutation,
58+
} from 'react-relay';
5359

5460
export const statusTagColor = {
5561
// V2 UPPERCASE enum values (VFolderOperationStatus)
@@ -115,6 +121,15 @@ interface VFolderNameCellProps {
115121
* (FR-2599) for the given vfolder instead of navigating away.
116122
*/
117123
onStartServiceFallback: (vfolderId: string) => void;
124+
/**
125+
* When true, the row-level "Deploy as service" action for model folders
126+
* is rendered disabled with a tooltip explaining that no compatible
127+
* deployment presets are available. Computed once at the table level
128+
* by `VFolderNodesV2` (via `deploymentRevisionPresets`) so we don't fire
129+
* one query per row. The `VFolderDeployModal` retains a `null`-return
130+
* fallback for the same condition as defense in depth.
131+
*/
132+
hasNoCompatiblePresets?: boolean;
118133
}
119134

120135
const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
@@ -124,6 +139,7 @@ const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
124139
onRestore,
125140
onDeleteForever,
126141
onStartServiceFallback,
142+
hasNoCompatiblePresets = false,
127143
}) => {
128144
'use memo';
129145
const { t } = useTranslation();
@@ -143,6 +159,10 @@ const VFolderNameCell: React.FC<VFolderNameCellProps> = ({
143159
key: 'start-service',
144160
title: t('modelService.DeployAsService'),
145161
icon: <BAIEndpointsIcon />,
162+
disabled: hasNoCompatiblePresets,
163+
disabledReason: hasNoCompatiblePresets
164+
? t('data.folders.NoCompatibleDeploymentPresets')
165+
: undefined,
146166
onClick: () => onStartServiceFallback(vfolderId),
147167
}
148168
: null,
@@ -488,6 +508,28 @@ const VFolderNodesV2: React.FC<VFolderNodesV2Props> = ({
488508

489509
const filteredVFolders = filterOutNullAndUndefined(vfolders);
490510

511+
// Project-scoped check: are any deployment presets available? Used to
512+
// gate the row-level "Deploy as service" action so model folders show a
513+
// disabled tooltip ("No compatible deployment presets…") instead of a
514+
// fully interactive button that opens a modal which then returns `null`
515+
// (FR-2831). Run once per table mount, not once per row.
516+
// TODO(needs-backend): once `deploymentRevisionPresets` exposes a
517+
// per-vfolder compatibility filter, narrow this check to the rows that
518+
// actually have a model folder and respect their compatibility.
519+
const { deploymentRevisionPresets } =
520+
useLazyLoadQuery<VFolderNodesV2DeploymentPresetsCheckQuery>(
521+
graphql`
522+
query VFolderNodesV2DeploymentPresetsCheckQuery {
523+
deploymentRevisionPresets(first: 0) {
524+
count
525+
}
526+
}
527+
`,
528+
{},
529+
{ fetchPolicy: 'store-or-network' },
530+
);
531+
const hasNoCompatiblePresets = (deploymentRevisionPresets?.count ?? 0) === 0;
532+
491533
const [commitDeleteMutation] = useMutation<VFolderNodesV2DeleteMutation>(
492534
graphql`
493535
mutation VFolderNodesV2DeleteMutation($vfolderId: UUID!) {
@@ -566,6 +608,7 @@ const VFolderNodesV2: React.FC<VFolderNodesV2Props> = ({
566608
return (
567609
<VFolderNameCell
568610
vfolder={vfolder}
611+
hasNoCompatiblePresets={hasNoCompatiblePresets}
569612
onShare={() => {
570613
vfolder?.ownership?.userId === currentUser?.uuid
571614
? setInviteFolderId(toLocalId(vfolder?.id ?? null))

resources/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@
790790
"MultipleFolderDeletedForever": "Permanently deleted {{count}} folders out of {{total}}.",
791791
"MultipleFolderRestored": "Selected {{ folderLength }} folders has been restored.",
792792
"Name": "Name",
793+
"NoCompatibleDeploymentPresets": "No compatible deployment presets are available for this folder.",
793794
"NoDeletePermission": "No delete permission",
794795
"NoFolderToDisplay": "No Folders to display",
795796
"NoRestorePermission": "No restore permission",

0 commit comments

Comments
 (0)