From a37b27f85e7e3420bb890043e2874bf9ef9e3408 Mon Sep 17 00:00:00 2001 From: Priti Sambandam Date: Mon, 16 Mar 2026 02:30:37 -0700 Subject: [PATCH 1/6] Adding knowledge hub list wizard with crud operations --- Localize/lang/strings.json | 40 ++ .../src/lib/core/knowledge/utils/helper.ts | 29 +- .../src/lib/core/knowledge/utils/queries.ts | 47 +- .../src/lib/ui/knowledge/modals/delete.tsx | 177 +++++++ .../src/lib/ui/knowledge/modals/styles.ts | 10 + .../lib/ui/knowledge/wizard/knowledgehub.tsx | 56 +- .../lib/ui/knowledge/wizard/knowledgelist.tsx | 484 ++++++++++++++++++ .../src/lib/ui/knowledge/wizard/styles.ts | 63 +++ .../src/utils/src/lib/models/knowledge.ts | 15 +- 9 files changed, 857 insertions(+), 64 deletions(-) create mode 100644 libs/designer/src/lib/ui/knowledge/modals/delete.tsx create mode 100644 libs/designer/src/lib/ui/knowledge/modals/styles.ts create mode 100644 libs/designer/src/lib/ui/knowledge/wizard/knowledgelist.tsx diff --git a/Localize/lang/strings.json b/Localize/lang/strings.json index bbfeff47d25..d9d2949896d 100644 --- a/Localize/lang/strings.json +++ b/Localize/lang/strings.json @@ -48,6 +48,8 @@ "/4vNBB": "Search logic apps...", "/5vL6M": "Run based on events in Azure services", "/88C7X": "At least 1 tool must be selected.", + "/8PUD5": "Confirm that you want to delete these hub artifacts? You can't undo this action. Deleting the hub will delete all artifacts under it.", + "/BY2cI": "Select row", "/EU/oJ": "Required. A string that contains the time zone name of the source time zone. See 'Default Time Zones' at 'https://go.microsoft.com/fwlink/?linkid=2238292'.", "/IVuGP": "Discard", "/KRvvg": "No values match your search.", @@ -146,6 +148,7 @@ "1GWzEL": "No results found for the specified filters", "1Gsrs4": "You can ask questions or describe changes you want to make to your workflow.", "1HhCtq": "Headers", + "1Jch8f": "Rename", "1KFpTX": "(UTC+03:00) Minsk", "1KMc+6": "The integer should be between [{min}, {max}]", "1LSKq8": "Basics", @@ -370,6 +373,7 @@ "6+7YiX": "Set up your MCP server with new workflows. Build them with connectors and actions from our catalog.", "6+L8qX": "Location", "6+XmJg": "Database", + "6/oxZR": "The following hub artifacts were deleted.", "62Ypnr": "Error loading operation data", "63/zYN": "Required. The object to check if it is greater than value being compared to.", "632t9E": "Secure inputs of the operation", @@ -494,6 +498,7 @@ "8KpZmj": "The system-assigned identity is unavailable because it's not enabled.", "8L+oIz": "Cannot render designer due to multiple triggers in definition.", "8LhQeL": "Switch to code view mode", + "8M2YfK": "Delete", "8ND+Yc": "Please enable managed identity for the logic app.", "8NFfuB": "Actions", "8NUqpR": "Describe how your flow should be changed. Add details where possible, including the connector to use and if any content should be included.", @@ -513,6 +518,7 @@ "8j+a0n": "With the asynchronous pattern, if the remote server indicates that the request is accepted for processing with a 202 (Accepted) response, the Logic Apps engine will keep polling the URL specified in the response's location header until reaching a terminal state.", "8lZGy+": "Chat is only available in production when authentication is enabled on the app. This ensures secure access to your workflow.", "8mDG0V": "The workflow has parameter validation errors in the following operations: {invalidNodes}", + "8mnvGL": "Last modified by", "8nnC5o": "The user-friendly name displayed for the workflow in the Azure portal.", "8opHew": "Combine Initialize Variables (preview)", "8p0yK8": "Run after", @@ -578,6 +584,7 @@ "AB+yPQ": "Connection details", "AEguAy": "Empty value", "AGCm1p": "Name", + "AIinB0": "Last modified date", "AJ+LDh": "Provide a unique, descriptive name and review the state type to ensure your workflows are properly configured.", "AKOkI2": "Service principal", "AMMfbt": "{count} Second", @@ -850,6 +857,7 @@ "GX3fkR": "New assertion", "GXFvm+": "To enable chat client for production, setup authentication.", "GYu5XE": "Failed to load run details", + "GYui13": "Continue editing", "GYvF54": "Referencing functions", "GZ8MDP": "{s1} of {s2}", "GbgqGL": "''{propertyName}'' is required", @@ -924,6 +932,7 @@ "IHzSSN": "Path parameters", "ILcDyX": "Head on over to the gallery page to see your template in action.", "IMWSjN": "Loading resources ...", + "IOAsSh": "Agent", "IOQVnL": "Workflow display name is required for Save.", "IPwWgu": "(UTC+02:00) Jerusalem", "IQyOth": "If available, dynamic content is automatically generated from the connectors and actions you choose for your flow.", @@ -970,6 +979,7 @@ "JKQuh+": "Logic app name", "JKZpcd": "Copilot chat canceled", "JKfEGS": "Create new", + "JLWOQY": "List of knowledge hubs", "JNQHws": "Required. A string that contains the time.", "JQBEOg": "Review + create", "JRsTtp": "Task timeline", @@ -1015,6 +1025,7 @@ "KZOa5l": "Cancel", "Kasmd1": "Go to workflow", "Kb5u9F": "About tab", + "KfHL/5": "Hub(s): {hubNames}", "KlDW+5": "(UTC+02:00) Beirut", "KmW31k": "Action is unreachable in flow structure", "KnjcUV": "Ignored", @@ -1306,6 +1317,7 @@ "QwAEWd": "Select a project", "QxEQwD": "Status", "R/aiRy": "(UTC+12:00) Coordinated Universal Time+12", + "R0Skk9": "Confirm that you want to delete this artifact? You can't undo this action.", "R7UxBX": "Learn more", "R7VvvJ": "Workflows", "RA4TUH": "Expand action", @@ -1471,6 +1483,7 @@ "UcFx/i": "Add description", "UcVYwq": "Create new", "Ud5V1C": "Create new", + "Uf1R8k": "Description", "Ufv5m9": "Schema node ''{nodeName}'' has an non-terminating connection chain", "Ug4sWZ": "Expand", "UgaIRz": "Required. The length of each chunk.", @@ -1582,6 +1595,7 @@ "X0/aJy": "Last 30 days", "X02GGK": "Tags", "X1TOAH": "Enter operation description", + "X2TtEI": "Artifact(s) name: {artifactNames}", "X2idLs": "(UTC-03:00) Montevideo", "X4gDhV": "Tenant", "X7X5ew": "Parameters", @@ -1780,6 +1794,8 @@ "_/4vNBB.comment": "Placeholder text for logic app search", "_/5vL6M.comment": "Azure events trigger category description", "_/88C7X.comment": "Error message when no tools are selected in selected tools mode", + "_/8PUD5.comment": "Content for the delete hub artifacts modal", + "_/BY2cI.comment": "Label for select row checkbox", "_/EU/oJ.comment": "Required string parameter for source time zone", "_/IVuGP.comment": "Button text for discard the dialog", "_/KRvvg.comment": "Label for when no values match search value.", @@ -1878,6 +1894,7 @@ "_1GWzEL.comment": "Text displayed when no results are found in the browse grid", "_1Gsrs4.comment": "Chatbot outro message when workflow editing is enabled", "_1HhCtq.comment": "headers", + "_1Jch8f.comment": "Label for the rename action", "_1KFpTX.comment": "Time zone value ", "_1KMc+6.comment": "Error validation message for integers being out of range", "_1LSKq8.comment": "Accessibility label for the basics section", @@ -2102,6 +2119,7 @@ "_6+7YiX.comment": "Description for creating new workflows", "_6+L8qX.comment": "Label for location field", "_6+XmJg.comment": "Title for the database section in basics tab for quick app create panel", + "_6/oxZR.comment": "Content for the toaster after successfully deleting hub artifacts, with the names of the deleted artifacts", "_62Ypnr.comment": "label for panel error", "_63/zYN.comment": "Required object parameter to compare to in less function", "_632t9E.comment": "description of the secure inputs setting", @@ -2226,6 +2244,7 @@ "_8KpZmj.comment": "error message for unsupported system-assigned managed identity", "_8L+oIz.comment": "This is an error message shown when a user tries to load a workflow defintion that contains Multiple entry points which isn't supported", "_8LhQeL.comment": "Label for editor toggle button when in expanded mode", + "_8M2YfK.comment": "Label for the delete action", "_8ND+Yc.comment": "Error Message for disabled managed identity", "_8NFfuB.comment": "Title text for actions in the suggested flow", "_8NUqpR.comment": "Chatbot prompt to edit the workflow description", @@ -2245,6 +2264,7 @@ "_8j+a0n.comment": "description of asynchronous pattern setting", "_8lZGy+.comment": "Production section description in info dialog", "_8mDG0V.comment": "Error message to show when there are invalid connections in the nodes.", + "_8mnvGL.comment": "Label for the last modified column", "_8nnC5o.comment": "Description for workflow display name field", "_8opHew.comment": "Title for the combine variable dialog. This is a preview feature.", "_8p0yK8.comment": "Button label for checking the action that this operation runs after", @@ -2310,6 +2330,7 @@ "_AB+yPQ.comment": "Header for popup containing connection details", "_AEguAy.comment": "Error message on expression evaluation", "_AGCm1p.comment": "Header for resource name", + "_AIinB0.comment": "Label for the last modified date column", "_AJ+LDh.comment": "General info displayed on basics tab for configuring workflow name and state type info - line 1.", "_AKOkI2.comment": "Dropdown text for service principal connection", "_AMMfbt.comment": "Second", @@ -2582,6 +2603,7 @@ "_GX3fkR.comment": "New Assertion Text", "_GXFvm+.comment": "Option 2 description when auth is not enabled", "_GYu5XE.comment": "Error message title when a single run fails to load", + "_GYui13.comment": "Button text for closing the delete hub artifacts modal", "_GYvF54.comment": "Label for referencing functions", "_GZ8MDP.comment": "Shows how many suggested flows there are", "_GbgqGL.comment": "Error message for missing property. Do not remove the double single quotes around the display name, as it is needed to wrap the placeholder text.", @@ -2656,6 +2678,7 @@ "_IHzSSN.comment": "Display name for relative path parameters in trigger outputs", "_ILcDyX.comment": "Content for the toaster for after publishing template.", "_IMWSjN.comment": "Loading text", + "_IOAsSh.comment": "Label for the agent column", "_IOQVnL.comment": "Hint message for workflow display name is required for save.", "_IPwWgu.comment": "Time zone value ", "_IQyOth.comment": "Section 1 of text for including dynamic content section", @@ -2702,6 +2725,7 @@ "_JKQuh+.comment": "Label for the logic app name field", "_JKZpcd.comment": "Chatbot card telling user that the AI response is being canceled", "_JKfEGS.comment": "Button to add a new connection", + "_JLWOQY.comment": "The aria label for the knowledge hubs table", "_JNQHws.comment": "Required string parameter that contains the time", "_JQBEOg.comment": "The tab label for the monitoring review and create tab on the create workflow panel", "_JRsTtp.comment": "Title for the monitoring timeline component.", @@ -2747,6 +2771,7 @@ "_KZOa5l.comment": "Label for cancel button", "_Kasmd1.comment": "Label to indicate go to the workflow", "_Kb5u9F.comment": "An accessibility label that describes the about tab", + "_KfHL/5.comment": "The name of the hub to be deleted, shown in the delete confirmation modal", "_KlDW+5.comment": "Time zone value ", "_KmW31k.comment": "Error message for disconnected nodes", "_KnjcUV.comment": "The status message to show in monitoring view.", @@ -3038,6 +3063,7 @@ "_QwAEWd.comment": "Select the project to use for this connection", "_QxEQwD.comment": "Status filter label", "_R/aiRy.comment": "Time zone value ", + "_R0Skk9.comment": "Content for the delete artifact", "_R7UxBX.comment": "Link text for learning more about knowledge base group", "_R7VvvJ.comment": "The tab label for the monitoring workflows tab on the configure template wizard", "_RA4TUH.comment": "Text indicating a menu button to expand an action in the designer", @@ -3203,6 +3229,7 @@ "_UcFx/i.comment": "Title for the trigger description dialog.", "_UcVYwq.comment": "Label for creating a new resource in the token field.", "_Ud5V1C.comment": "Button label for create agent parameter", + "_Uf1R8k.comment": "Label for the description column", "_Ufv5m9.comment": "Body text for an unconnected required schema card", "_Ug4sWZ.comment": "Expand to make the node bigger and show the contents.", "_UgaIRz.comment": "Required number parameter to get length of each chunk for chunk function", @@ -3314,6 +3341,7 @@ "_X0/aJy.comment": "Last 30 days filter", "_X02GGK.comment": "Title for the tags section in the template overview tab", "_X1TOAH.comment": "Placeholder text for operation description field", + "_X2TtEI.comment": "The name of the artifact to be deleted, shown in the delete confirmation modal", "_X2idLs.comment": "Time zone value ", "_X4gDhV.comment": "Tenant Label Display Name", "_X7X5ew.comment": "Workflow Parameters Title", @@ -3860,6 +3888,7 @@ "_iRe/g7.comment": "Hour of the day", "_iSiVB0.comment": "description of secure outputs setting", "_iTKrs8.comment": "Title for pagination setting", + "_iTtjSl.comment": "Title for the toaster after successfully deleting hub artifacts", "_iTz1lp.comment": "Text indicating a menu button to unpin a pinned action from the side panel", "_iU1OJh.comment": "An accessible label for expand toggle icon", "_iU5Fdh.comment": "Label for URI manipulation functions", @@ -4055,6 +4084,7 @@ "_meVkB6.comment": "Empty property name error message", "_mej02C.comment": "Time zone value ", "_merl0X.comment": "Placeholder for model dropdown", + "_mfpHrs.comment": "Label for the upload artifacts action", "_mgD2ZT.comment": "The tab label for the monitoring parameters tab on the operation panel", "_mjS/k1.comment": "description of suppress woers setting", "_mlU+AC.comment": "Header for the connections panel", @@ -4438,6 +4468,7 @@ "_uXecuj.comment": "Text to show missing required fields in the template.", "_uZpzqY.comment": "Chatbot report a bug button", "_uc/PoD.comment": "Required integer parameter to see how far in the past", + "_uc2g6Y.comment": "Title for the delete hub artifacts modal", "_uczA5c.comment": "Required text parameter to search startsWith function on", "_udnt8c.comment": "This is an option in a dropdown where users can select type Secure Object for their parameter.", "_uesaee.comment": "Expression", @@ -4518,6 +4549,7 @@ "_w/tTbg.comment": "Text displayed while loading knowledge hubs", "_w0pNyJ.comment": "Label text for agent key", "_w16qh+.comment": "Display name for queries in outputs", + "_w1QL1r.comment": "Button text for deleting the hub artifacts", "_w2VjJS.comment": "Aria label for closing the MCP server creation panel", "_w2rxzD.comment": "Text to explain that there are no executed tools in the agent iteration", "_w3BZ0u.comment": "Placeholder text for connector search input", @@ -4601,6 +4633,7 @@ "_xYyPR8.comment": "Label for description of custom uriPath Function", "_xd5jz/.comment": "Warning title for when unable to parse schema", "_xfIp1j.comment": "Cancelled status", + "_xfUoo5.comment": "Label for the status column", "_xfXUGz.comment": "Minute", "_xgV4pp.comment": "Text for the \"Select All\" option in a multiselect dropdown", "_xhBvXj.comment": "Button text for opening test panel", @@ -4689,6 +4722,7 @@ "_ztfbU8.comment": "The aria label for the operations table", "_zujc0T.comment": "Tooltip text for sorting results", "_zxF7g+.comment": "Sort by dropdown option of A to Z ascending", + "_zxFbNI.comment": "Content for the delete hub", "_zxe9hh.comment": "Required integer parameter to subtract minutes from time", "_zyaw99.comment": "Button text for deleting a server", "a1fbm6": "Chat availability information", @@ -5088,6 +5122,7 @@ "iRe/g7": "21", "iSiVB0": "Secure outputs of the operation and references of output properties", "iTKrs8": "Pagination", + "iTtjSl": "Successfully deleted the hub artifact(s).", "iTz1lp": "Unpin action", "iU1OJh": "Expand", "iU5Fdh": "Manipulation functions", @@ -5283,6 +5318,7 @@ "meVkB6": "Empty property name", "mej02C": "(UTC+08:30) Pyongyang", "merl0X": "Select a model", + "mfpHrs": "Upload artifacts", "mgD2ZT": "Summary", "mjS/k1": "Limit Logic Apps to not include workflow metadata headers in the outgoing request.", "mlU+AC": "Connections", @@ -5666,6 +5702,7 @@ "uXecuj": "Missing required fields:", "uZpzqY": "Sorry, Copilot is at capacity and temporarily unavailable — please try again in a little while.", "uc/PoD": "Required. The number of time units the desired time is in the past.", + "uc2g6Y": "Delete hub artifacts.", "uczA5c": "Required. The string that may contain the value.", "udnt8c": "Secure object", "uesaee": "Expression", @@ -5746,6 +5783,7 @@ "w/tTbg": "Loading...", "w0pNyJ": "Agent Key:", "w16qh+": "Queries", + "w1QL1r": "Delete", "w2VjJS": "Close MCP server creation panel", "w2rxzD": "This iteration has completed without any tool execution", "w3BZ0u": "Search...", @@ -5829,6 +5867,7 @@ "xYyPR8": "Returns the path from a URI. If path is not specified, returns '/'", "xd5jz/": "Can't parse the schema for the agent parameter.", "xfIp1j": "Cancelled", + "xfUoo5": "Upload status", "xfXUGz": "{count} Minute", "xgV4pp": "Select all", "xhBvXj": "Open test panel", @@ -5917,6 +5956,7 @@ "ztfbU8": "List of operations", "zujc0T": "Sort results", "zxF7g+": "A to Z, ascending", + "zxFbNI": "Confirm that you want to delete this hub? You can't undo this action. Deleting the hub will delete all artifacts under it.", "zxe9hh": "Required. The number of minutes to add. Can be negative to subtract minutes.", "zyaw99": "Delete" } diff --git a/libs/designer/src/lib/core/knowledge/utils/helper.ts b/libs/designer/src/lib/core/knowledge/utils/helper.ts index b12bebd0f54..f6efb59a5fc 100644 --- a/libs/designer/src/lib/core/knowledge/utils/helper.ts +++ b/libs/designer/src/lib/core/knowledge/utils/helper.ts @@ -7,7 +7,7 @@ export const createKnowledgeHub = async (siteResourceId: string, groupName: stri `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${groupName}`, 'PUT', { - 'api-version': '2025-11-01', + 'api-version': '2018-11-01', 'Content-Type': 'application/json', }, JSON.stringify({ description }) @@ -30,3 +30,30 @@ export const createKnowledgeHub = async (siteResourceId: string, groupName: stri }); } }; + +export const deleteKnowledgeHubArtifacts = async (siteResourceId: string, hubs: string[], artifacts: Record) => { + const promises: Promise[] = []; + + for (const hubName of hubs) { + promises.push( + ResourceService().executeResourceAction( + `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${hubName}`, + 'DELETE', + { 'api-version': '2018-11-01' } + ) + ); + } + + for (const artifactName of Object.keys(artifacts)) { + const hubName = artifacts[artifactName]; + promises.push( + ResourceService().executeResourceAction( + `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${hubName}/knowledgeArtifacts/${artifactName}`, + 'DELETE', + { 'api-version': '2018-11-01' } + ) + ); + } + + return Promise.all(promises); +}; diff --git a/libs/designer/src/lib/core/knowledge/utils/queries.ts b/libs/designer/src/lib/core/knowledge/utils/queries.ts index 0549391d300..fa75118107b 100644 --- a/libs/designer/src/lib/core/knowledge/utils/queries.ts +++ b/libs/designer/src/lib/core/knowledge/utils/queries.ts @@ -3,7 +3,6 @@ import { ConnectionService, equals, type KnowledgeHub, - type KnowledgeHubArtifact, type KnowledgeHubExtended, LogEntryLevel, LoggerService, @@ -24,21 +23,14 @@ export const useAllKnowledgeHubs = (siteResourceId: string) => { queryKey: ['knowledgehubs', siteResourceId.toLowerCase()], queryFn: async (): Promise => { try { - const response: any = await ResourceService().executeResourceAction( - `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub`, - 'GET', - { 'api-version': '2025-11-01' } + const response: any = await ResourceService().getResource( + `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgehubs`, + { 'api-version': '2018-11-01' } ); - const hubs = (response.value ?? []).sort((a: KnowledgeHub, b: KnowledgeHub) => a.name.localeCompare(b.name)); + const hubs = (response ?? []).sort((a: KnowledgeHub, b: KnowledgeHub) => a.name.localeCompare(b.name)); - const promises: Promise[] = hubs.map((hub: KnowledgeHub) => getArtifactsInHub(siteResourceId, hub.name)); - - const extendedHubs = await Promise.all(promises); - return hubs.map((hub: KnowledgeHub, index: number) => ({ - ...hub, - artifacts: extendedHubs[index], - })); + return hubs; } catch (errorResponse: any) { const error = errorResponse?.error || {}; @@ -57,35 +49,6 @@ export const useAllKnowledgeHubs = (siteResourceId: string) => { }); }; -export const getArtifactsInHub = async (siteResourceId: string, hubName: string) => { - const queryClient = getReactQueryClient(); - - return queryClient.fetchQuery( - ['knowledgeartifacts', siteResourceId.toLowerCase(), hubName.toLowerCase()], - async (): Promise => { - try { - const response: any = await ResourceService().executeResourceAction( - `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${hubName}/knowledgeArtifact`, - 'GET', - { 'api-version': '2025-11-01' } - ); - - return (response.value ?? []).sort((a: KnowledgeHubArtifact, b: KnowledgeHubArtifact) => a.name.localeCompare(b.name)); - } catch (errorResponse: any) { - const error = errorResponse?.error || {}; - // For now log the error and return empty list - LoggerService().log({ - level: LogEntryLevel.Error, - area: 'KnowledgeHub.listKnowledgeHubArtifacts', - error, - message: `Error while fetching knowledge artifacts for the app: ${siteResourceId}`, - }); - return []; - } - } - ); -}; - export const useConnection = () => { return useQuery({ queryKey: ['knowledgeconnection'], diff --git a/libs/designer/src/lib/ui/knowledge/modals/delete.tsx b/libs/designer/src/lib/ui/knowledge/modals/delete.tsx new file mode 100644 index 00000000000..be68f81633e --- /dev/null +++ b/libs/designer/src/lib/ui/knowledge/modals/delete.tsx @@ -0,0 +1,177 @@ +import { + Button, + Dialog, + DialogActions, + DialogBody, + DialogContent, + DialogSurface, + DialogTitle, + DialogTrigger, + tokens, + Text, +} from '@fluentui/react-components'; +import { useCallback, useMemo } from 'react'; +import { useIntl } from 'react-intl'; +import type { KnowledgeHubItem } from '../wizard/knowledgelist'; +import type { ServerNotificationData as NotificationData } from '../../mcp/servers/servers'; +import { useModalStyles } from './styles'; +import { deleteKnowledgeHubArtifacts } from 'lib/core/knowledge/utils/helper'; +import { LogEntryLevel, LoggerService } from '@microsoft/logic-apps-shared'; + +export const DeleteModal = ({ + selectedArtifacts, + resourceId, + onDelete, + onDismiss, +}: { selectedArtifacts: KnowledgeHubItem[]; resourceId: string; onDelete: (data: NotificationData) => void; onDismiss: () => void }) => { + const intl = useIntl(); + const hubsToDelete: string[] = selectedArtifacts.filter((item) => item.parentId === null).map((item) => item.name.toLowerCase()); + const artifactsToDelete: Record = selectedArtifacts + .filter((item) => item.parentId !== null) + .reduce( + (acc, item) => { + if (hubsToDelete.includes((item.parentId as string).toLowerCase())) { + acc[item.name.toLowerCase()] = item.parentId as string; + } + return acc; + }, + {} as Record + ); + + const hubNames = useMemo(() => hubsToDelete.join(', '), [hubsToDelete]); + const artifactNames = useMemo( + () => + Object.keys(artifactsToDelete) + .map((key) => `${key} (hub: ${artifactsToDelete[key]})`) + .join(', '), + [artifactsToDelete] + ); + const INTL_TEXT = { + title: intl.formatMessage({ + defaultMessage: 'Delete hub artifacts.', + id: 'uc2g6Y', + description: 'Title for the delete hub artifacts modal', + }), + multiArtifactsContent: intl.formatMessage({ + defaultMessage: `Confirm that you want to delete these hub artifacts? You can't undo this action. Deleting the hub will delete all artifacts under it.`, + id: '/8PUD5', + description: 'Content for the delete hub artifacts modal', + }), + hubContent: intl.formatMessage({ + defaultMessage: `Confirm that you want to delete this hub? You can't undo this action. Deleting the hub will delete all artifacts under it.`, + id: 'zxFbNI', + description: 'Content for the delete hub', + }), + artifactContent: intl.formatMessage({ + defaultMessage: `Confirm that you want to delete this artifact? You can't undo this action.`, + id: 'R0Skk9', + description: 'Content for the delete artifact', + }), + hubName: intl.formatMessage( + { + defaultMessage: 'Hub(s): {hubNames}', + id: 'KfHL/5', + description: 'The name of the hub to be deleted, shown in the delete confirmation modal', + }, + { hubNames } + ), + artifactName: intl.formatMessage( + { + defaultMessage: 'Artifact(s) name: {artifactNames}', + id: 'X2TtEI', + description: 'The name of the artifact to be deleted, shown in the delete confirmation modal', + }, + { artifactNames } + ), + deleteButtonText: intl.formatMessage({ + defaultMessage: 'Delete', + id: 'w1QL1r', + description: 'Button text for deleting the hub artifacts', + }), + closeButtonText: intl.formatMessage({ + defaultMessage: 'Continue editing', + id: 'GYui13', + description: 'Button text for closing the delete hub artifacts modal', + }), + successNotificationTitle: intl.formatMessage({ + defaultMessage: 'Successfully deleted the hub artifact(s).', + id: 'iTtjSl', + description: 'Title for the toaster after successfully deleting hub artifacts', + }), + successNotificationContent: intl.formatMessage({ + defaultMessage: 'The following hub artifacts were deleted.', + id: '6/oxZR', + description: 'Content for the toaster after successfully deleting hub artifacts, with the names of the deleted artifacts', + }), + }; + + const handleDelete = useCallback(async () => { + try { + await deleteKnowledgeHubArtifacts(resourceId, hubsToDelete, artifactsToDelete); + onDelete({ + title: INTL_TEXT.successNotificationTitle, + content: `${INTL_TEXT.successNotificationContent}\n${hubNames}${artifactNames ? `\n${artifactNames}` : ''}`, + }); + } catch (errorResponse: any) { + const error = errorResponse?.error || {}; + // For now log the error + LoggerService().log({ + level: LogEntryLevel.Error, + area: 'KnowledgeHub.deleteKnowledgeHubArtifact', + error, + message: `Error while deleting knowledge hub artifact for the app: ${resourceId}`, + }); + } + }, [ + INTL_TEXT.successNotificationContent, + INTL_TEXT.successNotificationTitle, + artifactNames, + artifactsToDelete, + hubNames, + hubsToDelete, + onDelete, + resourceId, + ]); + + const styles = useModalStyles(); + return ( + + + + {INTL_TEXT.title} + + {selectedArtifacts.length > 1 ? ( +
+ {INTL_TEXT.multiArtifactsContent} +
+ {INTL_TEXT.hubName} +
+ {INTL_TEXT.artifactName} +
+ ) : selectedArtifacts[0].parentId === null ? ( + INTL_TEXT.hubContent + ) : ( + INTL_TEXT.artifactContent + )} +
+ + + + + + +
+
+
+ ); +}; diff --git a/libs/designer/src/lib/ui/knowledge/modals/styles.ts b/libs/designer/src/lib/ui/knowledge/modals/styles.ts new file mode 100644 index 00000000000..8007e53a413 --- /dev/null +++ b/libs/designer/src/lib/ui/knowledge/modals/styles.ts @@ -0,0 +1,10 @@ +import { makeStyles } from '@fluentui/react-components'; + +export const useModalStyles = makeStyles({ + content: { + display: 'flex', + flexDirection: 'column', + gap: '20px', + padding: '20px', + }, +}); diff --git a/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx b/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx index ef15575ae47..d4e077c3044 100644 --- a/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx +++ b/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx @@ -3,7 +3,7 @@ import type { AppDispatch, RootState } from '../../../core/state/knowledge/store import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { setLayerHostSelector } from '@fluentui/react'; import { useIntl } from 'react-intl'; -import type { KnowledgeHub } from '@microsoft/logic-apps-shared'; +import type { KnowledgeHubExtended as KnowledgeHub } from '@microsoft/logic-apps-shared'; import { getStandardLogicAppId } from '../../../core/configuretemplate/utils/helper'; import { KnowledgePanelView, openPanelView } from '../../../core/state/knowledge/panelSlice'; import { @@ -36,6 +36,8 @@ import { LinkMultipleRegular, } from '@fluentui/react-icons'; import { CreateGroup } from '../modals/creategroup'; +import { type KnowledgeHubItem, KnowledgeList } from './knowledgelist'; +import { DeleteModal } from '../modals/delete'; export const KnowledgeHubWizard = () => { useEffect(() => setLayerHostSelector('#msla-layer-host'), []); @@ -99,6 +101,8 @@ export const KnowledgeHubWizard = () => { const [hubs, setHubs] = useState(undefined); const [showAddGroup, setShowAddGroup] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [selectedArtifacts, setSelectedArtifacts] = useState([]); useEffect(() => { if (allHubs && !isLoading) { @@ -106,24 +110,18 @@ export const KnowledgeHubWizard = () => { } }, [allHubs, isLoading]); - const handleDelete = useCallback(() => { - // Implement delete functionality here - }, []); - const handleAddFiles = useCallback(() => { // Implement add files functionality here }, []); - const handleAddGroup = useCallback(() => { - setShowAddGroup(true); - }, []); - const handleCloseAddGroup = useCallback(() => { - setShowAddGroup(false); - }, []); + const handleDeleteClick = useCallback(() => setShowDeleteModal(true), []); + const handleCloseDeleteModal = useCallback(() => setShowDeleteModal(false), []); + const handleOnDeleteComplete = useCallback(() => refetch(), [refetch]); + + const handleAddGroup = useCallback(() => setShowAddGroup(true), []); + const handleCloseAddGroup = useCallback(() => setShowAddGroup(false), []); - const handleRefreshHubs = useCallback(async () => { - await refetch(); - }, [refetch]); + const handleRefreshHubs = useCallback(async () => refetch(), [refetch]); const handleConnectionClick = useCallback(() => { dispatch(openPanelView({ panelView: connection ? KnowledgePanelView.EditConnection : KnowledgePanelView.CreateConnection })); @@ -174,12 +172,38 @@ export const KnowledgeHubWizard = () => { - - {hubs.length === 0 ? connection ? : :
{'Open the list view here'}
} + {hubs.length === 0 ? ( + connection ? ( + + ) : ( + + ) + ) : ( + + )} {showAddGroup ? : null} + {showDeleteModal ? ( + + ) : null}
void; + onUploadArtifacts: (hub: KnowledgeHub) => void; +}) => { + const intl = useIntl(); + const styles = useListStyles(); + const queryClient = useQueryClient(); + + const INTL_TEXT = { + tableAriaLabel: intl.formatMessage({ + defaultMessage: 'List of knowledge hubs', + id: 'JLWOQY', + description: 'The aria label for the knowledge hubs table', + }), + nameLabel: intl.formatMessage({ + defaultMessage: 'Name', + id: '5m1Ozg', + description: 'The label for the name column', + }), + typeLabel: intl.formatMessage({ + defaultMessage: 'Type', + id: 'XXOaU8', + description: 'The label for the type column', + }), + agentLabel: intl.formatMessage({ + defaultMessage: 'Agent', + id: 'IOAsSh', + description: 'Label for the agent column', + }), + descriptionLabel: intl.formatMessage({ + defaultMessage: 'Description', + id: 'Uf1R8k', + description: 'Label for the description column', + }), + lastModifiedLabel: intl.formatMessage({ + defaultMessage: 'Last modified by', + id: '8mnvGL', + description: 'Label for the last modified column', + }), + lastModifiedDateLabel: intl.formatMessage({ + defaultMessage: 'Last modified date', + id: 'AIinB0', + description: 'Label for the last modified date column', + }), + statusLabel: intl.formatMessage({ + defaultMessage: 'Upload status', + id: 'xfUoo5', + description: 'Label for the status column', + }), + renameLabel: intl.formatMessage({ + defaultMessage: 'Rename', + id: '1Jch8f', + description: 'Label for the rename action', + }), + uploadLabel: intl.formatMessage({ + defaultMessage: 'Upload artifacts', + id: 'mfpHrs', + description: 'Label for the upload artifacts action', + }), + deleteLabel: intl.formatMessage({ + defaultMessage: 'Delete', + id: '8M2YfK', + description: 'Label for the delete action', + }), + selectAll: intl.formatMessage({ + defaultMessage: 'Select all', + id: '5GHXCP', + description: 'Label for select all checkbox', + }), + selectRow: intl.formatMessage({ + defaultMessage: 'Select row', + id: '/BY2cI', + description: 'Label for select row checkbox', + }), + }; + + const [hubItems, setHubItems] = useState>(createHubItems(hubs)); + + useEffect(() => { + if (hubs) { + setHubItems(createHubItems(hubs)); + } + }, [hubs]); + + const columns = [ + createTableColumn({ + columnId: 'name', + }), + createTableColumn({ + columnId: 'type', + }), + createTableColumn({ + columnId: 'agent', + }), + createTableColumn({ + columnId: 'description', + }), + createTableColumn({ + columnId: 'modifiedBy', + }), + createTableColumn({ + columnId: 'modifiedDate', + }), + createTableColumn({ + columnId: 'status', + }), + createTableColumn({ + columnId: 'actions', + }), + ]; + + const items = useMemo( + () => + Object.values(hubItems).reduce((acc: KnowledgeHubItem[], item: KnowledgeHubItem) => { + acc.push(item); + + if (item.parentId === null && item.isExpanded) { + const childItems = hubs.find((hub) => hub.name === item.name)?.artifacts ?? []; + acc.push( + ...childItems.map((child) => ({ + name: child.name, + type: 'file', + description: child.description, + modifiedBy: '--', // Need to get this info from backend + modifiedDate: '--', // Need to get this info from backend + status: '--', // Need to determine how to get upload status + parentId: item.name, + isExpanded: false, + })) + ); + } + + return acc; + }, []), + [hubs, hubItems] + ); + + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [artifactToDelete, setArtifactToDelete] = useState(null); + const [selectedItems, setSelectedItems] = useState([]); + const setSelectedArtifactItems = useCallback( + (artifacts: string[]) => { + setSelectedItems(artifacts); + setSelectedArtifacts( + artifacts.map((artifactName) => { + const artifact = items.find((item) => equals(item.name, artifactName)); + return artifact!; + }) + ); + }, + [items, setSelectedArtifacts] + ); + + const { + getRows, + selection: { toggleRow, isRowSelected }, + } = useTableFeatures({ columns, items }, [ + useTableSelection({ + selectionMode: 'multiselect', + selectedItems: new Set(selectedItems), + onSelectionChange: (_, data) => setSelectedArtifactItems(Array.from(data.selectedItems, String)), + }), + ]); + + const rows = getRows((row) => { + const selected = isRowSelected(row.item.name); + return { + ...row, + onClick: (e: React.MouseEvent) => toggleRow(e, row.item.name), + onKeyDown: (e: React.KeyboardEvent) => { + if (e.key === ' ') { + e.preventDefault(); + toggleRow(e, row.item.name); + } + }, + selected, + appearance: selected ? ('brand' as const) : ('none' as const), + }; + }); + + const allRowsSelected = useMemo(() => { + return !rows?.filter((row) => !row.selected)?.length; + }, [rows]); + + const toggleAllRows = useCallback(() => { + setSelectedArtifactItems(allRowsSelected ? [] : items.map((item) => item.name)); + }, [setSelectedArtifactItems, items, allRowsSelected]); + + const toggleAllKeydown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === ' ') { + toggleAllRows(); + e.preventDefault(); + } + }, + [toggleAllRows] + ); + + const handleExpandCollapse = useCallback((item: KnowledgeHubItem, event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + setHubItems((prev) => ({ + ...prev, + [item.name]: { + ...prev[item.name], + isExpanded: !prev[item.name].isExpanded, + }, + })); + }, []); + + const handleContextMenuClick = useCallback((event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + }, []); + + const handleRename = useCallback( + (item: KnowledgeHubItem, event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + // For now just log the action, need to implement rename logic + // Implement rename logic here, possibly opening a dialog to enter new name + console.log('Rename item', item, selectedItems); + }, + [selectedItems] + ); + + const handleDelete = useCallback(async (item: KnowledgeHubItem, event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + setArtifactToDelete(item); + setShowDeleteModal(true); + }, []); + const handleCloseDeleteModal = useCallback(() => setShowDeleteModal(false), []); + const handleOnDeleteComplete = useCallback(() => { + const itemDeleted = artifactToDelete; + setShowDeleteModal(false); + + if (!itemDeleted) { + return; + } + + if (selectedItems.includes(itemDeleted.name)) { + setSelectedArtifactItems(selectedItems.filter((name) => name !== itemDeleted.name)); + } + setArtifactToDelete(null); + queryClient.setQueryData(['knowledgehubs', resourceId], (oldData: KnowledgeHub[] | undefined) => { + if (oldData) { + if (itemDeleted.parentId === null) { + // Hub group deleted, remove the whole hub + return oldData.filter((hub) => !equals(hub.name, itemDeleted.name)); + } + // Artifact deleted, remove the artifact from the hub + return oldData.map((hub) => { + if (equals(hub.name, itemDeleted.parentId)) { + return { + ...hub, + artifacts: hub.artifacts.filter((artifact) => !equals(artifact.name, itemDeleted.name)), + }; + } + return hub; + }); + } + + return oldData; + }); + }, [artifactToDelete, queryClient, resourceId, selectedItems, setSelectedArtifactItems]); + + const handleUploadArtifacts = useCallback( + (item: KnowledgeHubItem, event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + onUploadArtifacts(hubs.find((hub) => hub.name === item.name)!); + }, + [hubs, onUploadArtifacts] + ); + + const renderNameCell = useCallback( + (item: KnowledgeHubItem) => { + const isHubGroup = item.parentId === null; + const className = isHubGroup ? undefined : styles.artifactNameCell; + + return ( + +
+ {isHubGroup ? ( +
+
+ ); + }, + [handleExpandCollapse, styles.artifactNameCell, styles.nameCell, styles.nameText] + ); + + const renderTextCell = useCallback( + (text: string) => ( + + + {text} + + + ), + [] + ); + + if (!items.length) { + return null; + } + + return ( +
+ + + + + {INTL_TEXT.nameLabel} + {INTL_TEXT.typeLabel} + {INTL_TEXT.agentLabel} + {INTL_TEXT.descriptionLabel} + {INTL_TEXT.lastModifiedLabel} + {INTL_TEXT.lastModifiedDateLabel} + {INTL_TEXT.statusLabel} + {/* Actions column, no header */} + + + + {rows.map(({ item, selected, onClick, onKeyDown, appearance }) => ( + + + {renderNameCell(item)} + {renderTextCell(item.type)} + {renderTextCell(item.agent ?? '')} + {renderTextCell(item.description)} + {renderTextCell(item.modifiedBy)} + {renderTextCell(item.modifiedDate)} + {renderTextCell(item.status)} + + + + + + + ))} + +
+ {showDeleteModal && artifactToDelete ? ( + + ) : null} +
+ ); +}; + +const createHubItems = (hubs: KnowledgeHub[]): Record => + hubs.reduce( + (result: Record, hub: KnowledgeHub) => { + result[hub.name] = { + name: hub.name, + type: 'folder', + agent: '--', // Need to determine how to get agent info + description: hub.description, + modifiedBy: '--', // Need to get this info from backend + modifiedDate: '--', // Need to get this info from backend + status: '--', // Need to determine how to get upload status + parentId: null, + isExpanded: false, + }; + return result; + }, + {} as Record + ); diff --git a/libs/designer/src/lib/ui/knowledge/wizard/styles.ts b/libs/designer/src/lib/ui/knowledge/wizard/styles.ts index 41ee6dd69a1..1c8c1741bb6 100644 --- a/libs/designer/src/lib/ui/knowledge/wizard/styles.ts +++ b/libs/designer/src/lib/ui/knowledge/wizard/styles.ts @@ -41,3 +41,66 @@ export const useWizardStyles = makeStyles({ padding: '10px 0', }, }); + +export const useListStyles = makeStyles({ + tableStyle: { + width: '100%', + margin: '0 auto', + }, + + icon: { + marginRight: '8px', + }, + + iconsCell: { + textAlign: 'right', + }, + + nameCell: { + display: 'flex', + }, + + nameText: { + display: 'flex', + gap: '8px', + marginTop: '6px', + }, + + hubNameCell: {}, + + artifactNameCell: { + marginLeft: '32px', + }, + + table: { + width: '100%', + borderCollapse: 'separate', + borderSpacing: '0 8px', + }, + + tableHeader: { + borderBottom: '1px solid #E1DFDD', + }, + + tableHeaderCell: { + fontWeight: '600', + color: '#605E5C', + paddingBottom: '8px', + }, + + tableRow: { + backgroundColor: '#FFFFFF', + boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.2)', + borderRadius: '4px', + }, + + toolNameCell: { + paddingTop: '6px', + alignItems: 'center', + display: 'flex', + }, + + lastCell: { + width: '8%', + }, +}); diff --git a/libs/logic-apps-shared/src/utils/src/lib/models/knowledge.ts b/libs/logic-apps-shared/src/utils/src/lib/models/knowledge.ts index 8e5339f131b..9ec8dfaee05 100644 --- a/libs/logic-apps-shared/src/utils/src/lib/models/knowledge.ts +++ b/libs/logic-apps-shared/src/utils/src/lib/models/knowledge.ts @@ -1,20 +1,25 @@ export interface KnowledgeHub { + id: string; name: string; description: string; + partitionKey: string; + createdAt: string; } export interface KnowledgeHubArtifact { + id: string; name: string; description: string; - type?: ArtifactType; - contentKind?: ContentKind; - contentStream?: any; - creationStatus?: ArtifactCreationStatus; + knowledgeHubId: string; + artifactSource: ArtifactType; + uploadStatus: ArtifactCreationStatus; + partitionKey: string; + createdAt: string; } export const ArtifactCreationStatus = { Initialized: 'Initialized', - Processing: 'Processing', + InProgress: 'InProgress', Completed: 'Completed', Failed: 'Failed', }; From 277c85453dc2a31753f397e04c4b60fccb99a751 Mon Sep 17 00:00:00 2001 From: Priti Sambandam Date: Tue, 17 Mar 2026 02:31:04 -0700 Subject: [PATCH 2/6] Adding selection behavior --- Localize/lang/strings.json | 16 +- .../src/lib/core/knowledge/utils/helper.ts | 6 +- .../src/lib/ui/knowledge/modals/delete.tsx | 20 +- .../src/lib/ui/knowledge/modals/styles.ts | 5 +- .../lib/ui/knowledge/wizard/knowledgehub.tsx | 5 +- .../lib/ui/knowledge/wizard/knowledgelist.tsx | 348 ++++++++++++------ .../src/lib/ui/knowledge/wizard/styles.ts | 21 +- 7 files changed, 289 insertions(+), 132 deletions(-) diff --git a/Localize/lang/strings.json b/Localize/lang/strings.json index d9d2949896d..073486a031a 100644 --- a/Localize/lang/strings.json +++ b/Localize/lang/strings.json @@ -518,7 +518,6 @@ "8j+a0n": "With the asynchronous pattern, if the remote server indicates that the request is accepted for processing with a 202 (Accepted) response, the Logic Apps engine will keep polling the URL specified in the response's location header until reaching a terminal state.", "8lZGy+": "Chat is only available in production when authentication is enabled on the app. This ensures secure access to your workflow.", "8mDG0V": "The workflow has parameter validation errors in the following operations: {invalidNodes}", - "8mnvGL": "Last modified by", "8nnC5o": "The user-friendly name displayed for the workflow in the Azure portal.", "8opHew": "Combine Initialize Variables (preview)", "8p0yK8": "Run after", @@ -566,6 +565,7 @@ "9bCLPz": "Loading API Management APIs...", "9bQctz": "Validation failed for workflows:", "9djnqI": "Returns the result from adding the two numbers", + "9euy52": "Complete", "9gb/xS": "Create", "9hKeBq": "Select an Azure OpenAI resource", "9klmbJ": "Save", @@ -584,7 +584,6 @@ "AB+yPQ": "Connection details", "AEguAy": "Empty value", "AGCm1p": "Name", - "AIinB0": "Last modified date", "AJ+LDh": "Provide a unique, descriptive name and review the state type to ensure your workflows are properly configured.", "AKOkI2": "Service principal", "AMMfbt": "{count} Second", @@ -1068,6 +1067,7 @@ "Lnqh6h": "Bold (Ctrl+B)", "LoGUT3": "When used inside for-each loop, this function returns the current item of the specified loop.", "LpPNAD": "Add", + "Lsac0i": "Created", "LtbkS7": "Returns the start of the day for the passed-in string timestamp.", "Lu+3Y4": "Upload chunk size", "LuIkbo": "Expanding actions...", @@ -1595,7 +1595,6 @@ "X0/aJy": "Last 30 days", "X02GGK": "Tags", "X1TOAH": "Enter operation description", - "X2TtEI": "Artifact(s) name: {artifactNames}", "X2idLs": "(UTC-03:00) Montevideo", "X4gDhV": "Tenant", "X7X5ew": "Parameters", @@ -2264,7 +2263,6 @@ "_8j+a0n.comment": "description of asynchronous pattern setting", "_8lZGy+.comment": "Production section description in info dialog", "_8mDG0V.comment": "Error message to show when there are invalid connections in the nodes.", - "_8mnvGL.comment": "Label for the last modified column", "_8nnC5o.comment": "Description for workflow display name field", "_8opHew.comment": "Title for the combine variable dialog. This is a preview feature.", "_8p0yK8.comment": "Button label for checking the action that this operation runs after", @@ -2312,6 +2310,7 @@ "_9bCLPz.comment": "Loading API Management APIs...", "_9bQctz.comment": "The error title for the workflows tab", "_9djnqI.comment": "Label for description of custom add Function", + "_9euy52.comment": "Text to indicate that the artifact upload is completed", "_9gb/xS.comment": "Button text for creating a group", "_9hKeBq.comment": "Select the Azure Cognitive Service Open AI resource to use for this connection", "_9klmbJ.comment": "Button text for saving changes for parameter in the customize parameter panel", @@ -2330,7 +2329,6 @@ "_AB+yPQ.comment": "Header for popup containing connection details", "_AEguAy.comment": "Error message on expression evaluation", "_AGCm1p.comment": "Header for resource name", - "_AIinB0.comment": "Label for the last modified date column", "_AJ+LDh.comment": "General info displayed on basics tab for configuring workflow name and state type info - line 1.", "_AKOkI2.comment": "Dropdown text for service principal connection", "_AMMfbt.comment": "Second", @@ -2814,6 +2812,7 @@ "_Lnqh6h.comment": "Command for bold text for non-mac users", "_LoGUT3.comment": "Label for description of custom item Function", "_LpPNAD.comment": "label to add a condition", + "_Lsac0i.comment": "Label for the created date column", "_LtbkS7.comment": "Label for the description of a custom 'startOfDay' function", "_Lu+3Y4.comment": "label for upload chunk size", "_LuIkbo.comment": "This is the text that is displayed when the user is expanding collapsed actions", @@ -3341,7 +3340,6 @@ "_X0/aJy.comment": "Last 30 days filter", "_X02GGK.comment": "Title for the tags section in the template overview tab", "_X1TOAH.comment": "Placeholder text for operation description field", - "_X2TtEI.comment": "The name of the artifact to be deleted, shown in the delete confirmation modal", "_X2idLs.comment": "Time zone value ", "_X4gDhV.comment": "Tenant Label Display Name", "_X7X5ew.comment": "Workflow Parameters Title", @@ -3696,6 +3694,7 @@ "_eT+b9W.comment": "Required number parameter to be divided by in div function", "_eTW4SD.comment": "Subtitle text for description", "_eUxnDx.comment": "Display name for status code in outputs", + "_eW6zWL.comment": "The name of the artifact to be deleted, shown in the delete confirmation modal", "_eWG113.comment": "Title for the MCP Server workflows section", "_eXWIo2.comment": "Description for parameter default value field", "_eXcejw.comment": "Running status", @@ -3749,6 +3748,7 @@ "_flNr70.comment": "Title for the connection details section in basics tab for quick app create panel", "_fmm7Ik.comment": "Token picker mode to insert expressions", "_fp8Ry3.comment": "Time zone value ", + "_fs92Nu.comment": "Text to indicate that the artifact upload has failed", "_fsRie2.comment": "Description for workflow summary field", "_ft8BH8.comment": "Seconds", "_fvGvnA.comment": "Chatbot error message", @@ -4162,6 +4162,7 @@ "_oAFcW6.comment": "Required string parameter to be decoded using decodeDataUri function", "_oBAL2F.comment": "Days", "_oBK3A4.comment": "Accessible label for editable expression token", + "_oBWuQA.comment": "Text to indicate that the artifact upload is in progress", "_oChTO9.comment": "Accessibility label for the select workflow row checkbox", "_oDHXKh.comment": "Display name for item output", "_oFq3ng.comment": "Assertions Panel Title", @@ -4930,6 +4931,7 @@ "eT+b9W": "Required. The number to divide the Dividend by.", "eTW4SD": "Description", "eUxnDx": "Status code", + "eW6zWL": "Artifact(s): {artifactNames}", "eWG113": "Workflows", "eXWIo2": "Pre-filled value used if the user doesn't enter anything.", "eXcejw": "In progress", @@ -4983,6 +4985,7 @@ "flNr70": "Details", "fmm7Ik": "Function", "fp8Ry3": "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", + "fs92Nu": "Error", "fsRie2": "A short overview of what the template does.", "ft8BH8": "{count} Seconds", "fvGvnA": "Sorry, something went wrong. Please try again.", @@ -5396,6 +5399,7 @@ "oAFcW6": "Required. The dataURI to decode into a binary representation.", "oBAL2F": "{count} Days", "oBK3A4": "Edit {tokenTitle} expression", + "oBWuQA": "In-progress", "oChTO9": "Select workflow row checkbox label", "oDHXKh": "Item", "oFq3ng": "Assertions", diff --git a/libs/designer/src/lib/core/knowledge/utils/helper.ts b/libs/designer/src/lib/core/knowledge/utils/helper.ts index f6efb59a5fc..49a2ea7a505 100644 --- a/libs/designer/src/lib/core/knowledge/utils/helper.ts +++ b/libs/designer/src/lib/core/knowledge/utils/helper.ts @@ -4,7 +4,7 @@ import { getReactQueryClient } from '../../ReactQueryProvider'; export const createKnowledgeHub = async (siteResourceId: string, groupName: string, description: string) => { try { const response = await ResourceService().executeResourceAction( - `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${groupName}`, + `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/${groupName}`, 'PUT', { 'api-version': '2018-11-01', @@ -37,7 +37,7 @@ export const deleteKnowledgeHubArtifacts = async (siteResourceId: string, hubs: for (const hubName of hubs) { promises.push( ResourceService().executeResourceAction( - `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${hubName}`, + `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/${hubName}`, 'DELETE', { 'api-version': '2018-11-01' } ) @@ -48,7 +48,7 @@ export const deleteKnowledgeHubArtifacts = async (siteResourceId: string, hubs: const hubName = artifacts[artifactName]; promises.push( ResourceService().executeResourceAction( - `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHub/${hubName}/knowledgeArtifacts/${artifactName}`, + `${siteResourceId}/hostruntime/runtime/webhooks/workflow/api/management/knowledgeHubs/${hubName}/artifacts/${artifactName}`, 'DELETE', { 'api-version': '2018-11-01' } ) diff --git a/libs/designer/src/lib/ui/knowledge/modals/delete.tsx b/libs/designer/src/lib/ui/knowledge/modals/delete.tsx index be68f81633e..4a99472c023 100644 --- a/libs/designer/src/lib/ui/knowledge/modals/delete.tsx +++ b/libs/designer/src/lib/ui/knowledge/modals/delete.tsx @@ -15,7 +15,7 @@ import { useIntl } from 'react-intl'; import type { KnowledgeHubItem } from '../wizard/knowledgelist'; import type { ServerNotificationData as NotificationData } from '../../mcp/servers/servers'; import { useModalStyles } from './styles'; -import { deleteKnowledgeHubArtifacts } from 'lib/core/knowledge/utils/helper'; +import { deleteKnowledgeHubArtifacts } from '../../../core/knowledge/utils/helper'; import { LogEntryLevel, LoggerService } from '@microsoft/logic-apps-shared'; export const DeleteModal = ({ @@ -30,7 +30,7 @@ export const DeleteModal = ({ .filter((item) => item.parentId !== null) .reduce( (acc, item) => { - if (hubsToDelete.includes((item.parentId as string).toLowerCase())) { + if (!hubsToDelete.includes((item.parentId as string).toLowerCase())) { acc[item.name.toLowerCase()] = item.parentId as string; } return acc; @@ -77,8 +77,8 @@ export const DeleteModal = ({ ), artifactName: intl.formatMessage( { - defaultMessage: 'Artifact(s) name: {artifactNames}', - id: 'X2TtEI', + defaultMessage: 'Artifact(s): {artifactNames}', + id: 'eW6zWL', description: 'The name of the artifact to be deleted, shown in the delete confirmation modal', }, { artifactNames } @@ -112,6 +112,7 @@ export const DeleteModal = ({ title: INTL_TEXT.successNotificationTitle, content: `${INTL_TEXT.successNotificationContent}\n${hubNames}${artifactNames ? `\n${artifactNames}` : ''}`, }); + onDismiss(); } catch (errorResponse: any) { const error = errorResponse?.error || {}; // For now log the error @@ -130,6 +131,7 @@ export const DeleteModal = ({ hubNames, hubsToDelete, onDelete, + onDismiss, resourceId, ]); @@ -144,9 +146,13 @@ export const DeleteModal = ({
{INTL_TEXT.multiArtifactsContent}
- {INTL_TEXT.hubName} -
- {INTL_TEXT.artifactName} + {hubsToDelete.length > 0 && ( + <> + {INTL_TEXT.hubName} +
+ + )} + {Object.keys(artifactsToDelete).length > 0 && {INTL_TEXT.artifactName}}
) : selectedArtifacts[0].parentId === null ? ( INTL_TEXT.hubContent diff --git a/libs/designer/src/lib/ui/knowledge/modals/styles.ts b/libs/designer/src/lib/ui/knowledge/modals/styles.ts index 8007e53a413..e5384cd4c7c 100644 --- a/libs/designer/src/lib/ui/knowledge/modals/styles.ts +++ b/libs/designer/src/lib/ui/knowledge/modals/styles.ts @@ -1,10 +1,9 @@ -import { makeStyles } from '@fluentui/react-components'; +import { makeStyles, tokens } from '@fluentui/react-components'; export const useModalStyles = makeStyles({ content: { display: 'flex', flexDirection: 'column', - gap: '20px', - padding: '20px', + paddingTop: tokens.spacingVerticalL, }, }); diff --git a/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx b/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx index d4e077c3044..770b1bb0f38 100644 --- a/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx +++ b/libs/designer/src/lib/ui/knowledge/wizard/knowledgehub.tsx @@ -116,7 +116,10 @@ export const KnowledgeHubWizard = () => { const handleDeleteClick = useCallback(() => setShowDeleteModal(true), []); const handleCloseDeleteModal = useCallback(() => setShowDeleteModal(false), []); - const handleOnDeleteComplete = useCallback(() => refetch(), [refetch]); + const handleOnDeleteComplete = useCallback(async () => { + await refetch(); + setSelectedArtifacts([]); + }, [refetch]); const handleAddGroup = useCallback(() => setShowAddGroup(true), []); const handleCloseAddGroup = useCallback(() => setShowAddGroup(false), []); diff --git a/libs/designer/src/lib/ui/knowledge/wizard/knowledgelist.tsx b/libs/designer/src/lib/ui/knowledge/wizard/knowledgelist.tsx index 799c72e0a4a..64675b964ca 100644 --- a/libs/designer/src/lib/ui/knowledge/wizard/knowledgelist.tsx +++ b/libs/designer/src/lib/ui/knowledge/wizard/knowledgelist.tsx @@ -1,3 +1,4 @@ +import type React from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Text, @@ -19,42 +20,33 @@ import { useTableSelection, createTableColumn, TableSelectionCell, + tokens, } from '@fluentui/react-components'; import { + ArrowSyncCircle20Regular, ArrowUpload24Regular, + CheckmarkCircle20Regular, ChevronDownRegular, ChevronRightRegular, Delete24Regular, - Document20Regular, + DocumentText20Regular, Edit24Regular, - Folder20Regular, + FolderOpen20Regular, MoreHorizontal20Regular, + SubtractCircle20Regular, } from '@fluentui/react-icons'; import { useIntl } from 'react-intl'; -import { equals, type KnowledgeHubExtended as KnowledgeHub } from '@microsoft/logic-apps-shared'; +import { ArtifactCreationStatus, equals, getPropertyValue, type KnowledgeHubExtended as KnowledgeHub } from '@microsoft/logic-apps-shared'; import { useListStyles } from '../wizard/styles'; import { useQueryClient } from '@tanstack/react-query'; import { DeleteModal } from '../modals/delete'; -const toolTableCellStyles = { - border: 'none', - paddingBottom: '8px', -}; -const toolNameCellStyles = { - paddingTop: '6px', - alignItems: 'center', -}; -const lastCellStyles = { - width: '8%', -}; - export interface KnowledgeHubItem { + id: string; name: string; type: string; - agent?: string; description: string; - modifiedBy: string; - modifiedDate: string; + createdDate: string; status: string; parentId: string | null; isExpanded: boolean; @@ -101,15 +93,10 @@ export const KnowledgeList = ({ id: 'Uf1R8k', description: 'Label for the description column', }), - lastModifiedLabel: intl.formatMessage({ - defaultMessage: 'Last modified by', - id: '8mnvGL', - description: 'Label for the last modified column', - }), - lastModifiedDateLabel: intl.formatMessage({ - defaultMessage: 'Last modified date', - id: 'AIinB0', - description: 'Label for the last modified date column', + createdDateLabel: intl.formatMessage({ + defaultMessage: 'Created', + id: 'Lsac0i', + description: 'Label for the created date column', }), statusLabel: intl.formatMessage({ defaultMessage: 'Upload status', @@ -141,13 +128,28 @@ export const KnowledgeList = ({ id: '/BY2cI', description: 'Label for select row checkbox', }), + inProgressStatus: intl.formatMessage({ + defaultMessage: 'In-progress', + id: 'oBWuQA', + description: 'Text to indicate that the artifact upload is in progress', + }), + completedStatus: intl.formatMessage({ + defaultMessage: 'Complete', + id: '9euy52', + description: 'Text to indicate that the artifact upload is completed', + }), + failedStatus: intl.formatMessage({ + defaultMessage: 'Error', + id: 'fs92Nu', + description: 'Text to indicate that the artifact upload has failed', + }), }; - const [hubItems, setHubItems] = useState>(createHubItems(hubs)); + const [allItems, setAllItems] = useState>(createAllItems(hubs)); useEffect(() => { if (hubs) { - setHubItems(createHubItems(hubs)); + setAllItems(createAllItems(hubs)); } }, [hubs]); @@ -158,17 +160,11 @@ export const KnowledgeList = ({ createTableColumn({ columnId: 'type', }), - createTableColumn({ - columnId: 'agent', - }), createTableColumn({ columnId: 'description', }), createTableColumn({ - columnId: 'modifiedBy', - }), - createTableColumn({ - columnId: 'modifiedDate', + columnId: 'createdDate', }), createTableColumn({ columnId: 'status', @@ -178,30 +174,24 @@ export const KnowledgeList = ({ }), ]; + // Getting the viewable items based on the expanded/collapsed state of the hubs const items = useMemo( () => - Object.values(hubItems).reduce((acc: KnowledgeHubItem[], item: KnowledgeHubItem) => { - acc.push(item); + Object.values(allItems).reduce((acc: KnowledgeHubItem[], item: KnowledgeHubItem) => { + if (item.parentId === null) { + acc.push(item); + } if (item.parentId === null && item.isExpanded) { - const childItems = hubs.find((hub) => hub.name === item.name)?.artifacts ?? []; - acc.push( - ...childItems.map((child) => ({ - name: child.name, - type: 'file', - description: child.description, - modifiedBy: '--', // Need to get this info from backend - modifiedDate: '--', // Need to get this info from backend - status: '--', // Need to determine how to get upload status - parentId: item.name, - isExpanded: false, - })) + const childItems = (hubs.find((hub) => hub.name === item.name)?.artifacts ?? []).map( + (artifact) => `${item.name.toLowerCase()}-${artifact.name.toLowerCase()}` ); + acc.push(...childItems.map((child) => allItems[child])); } return acc; }, []), - [hubs, hubItems] + [hubs, allItems] ); const [showDeleteModal, setShowDeleteModal] = useState(false); @@ -210,36 +200,80 @@ export const KnowledgeList = ({ const setSelectedArtifactItems = useCallback( (artifacts: string[]) => { setSelectedItems(artifacts); - setSelectedArtifacts( - artifacts.map((artifactName) => { - const artifact = items.find((item) => equals(item.name, artifactName)); - return artifact!; - }) - ); + setSelectedArtifacts(artifacts.map((artifactName) => getPropertyValue(allItems, artifactName))); }, - [items, setSelectedArtifacts] + [allItems, setSelectedArtifacts] ); - const { - getRows, - selection: { toggleRow, isRowSelected }, - } = useTableFeatures({ columns, items }, [ - useTableSelection({ - selectionMode: 'multiselect', - selectedItems: new Set(selectedItems), - onSelectionChange: (_, data) => setSelectedArtifactItems(Array.from(data.selectedItems, String)), - }), - ]); + const { getRows } = useTableFeatures({ columns, items }, [useTableSelection({ selectionMode: 'multiselect' })]); + const isRowSelected = useCallback((id: string) => !!selectedItems.find((item) => equals(item, id)), [selectedItems]); + const allRowsSelected = useMemo(() => items.every((item) => isRowSelected(item.id)), [items, isRowSelected]); + const someRowsSelected = useMemo(() => items.some((item) => isRowSelected(item.id)), [items, isRowSelected]); + + const toggleRowItem = useCallback( + (e: React.MouseEvent | React.KeyboardEvent, item: KnowledgeHubItem) => { + const selected = isRowSelected(item.id); + const isHubGroup = item.parentId === null; + const artifactsInHub = isHubGroup ? getArtifactItemsInHub(item, allItems) : []; + const artifactsToToggleState: string[] = []; + + if (isHubGroup) { + if (selected) { + // If the hub group is already selected, unselect the hub group and all its artifacts + for (const artifact of artifactsInHub) { + if (isRowSelected(artifact.id)) { + artifactsToToggleState.push(artifact.id); + } + } + } else { + // If the hub group is not selected, select the hub group and all its artifacts + for (const artifact of artifactsInHub) { + if (!isRowSelected(artifact.id)) { + artifactsToToggleState.push(artifact.id); + } + } + } + } else { + const hubItem = getPropertyValue(allItems, item.parentId as string); + if (hubItem) { + const artifactsInSameHub = getArtifactItemsInHub(hubItem, allItems); + const selectedArtifactsInSameHub = artifactsInSameHub.filter((artifact) => isRowSelected(artifact.id)).length; + + if (!selected && selectedArtifactsInSameHub === artifactsInSameHub.length - 1 && !isRowSelected(hubItem.id)) { + // If the artifact is not selected and it's the last unselected artifact in the hub, select the hub group + artifactsToToggleState.push(hubItem.id); + } else if (selected && selectedArtifactsInSameHub === artifactsInSameHub.length && isRowSelected(hubItem.id)) { + // If the artifact is selected and it's the only selected artifact in the hub, unselect the hub group + artifactsToToggleState.push(hubItem.id); + } + } + } + + artifactsToToggleState.push(item.id); + + let finalSelectedItems: string[] = selectedItems.slice(); + for (const id of artifactsToToggleState) { + if (selectedItems.includes(id)) { + finalSelectedItems = finalSelectedItems.filter((i) => !equals(i, id)); + } else { + finalSelectedItems.push(id); + } + } + + setSelectedArtifactItems(finalSelectedItems); + }, + [allItems, isRowSelected, selectedItems, setSelectedArtifactItems] + ); const rows = getRows((row) => { - const selected = isRowSelected(row.item.name); + const selected = isRowSelected(row.item.id); return { ...row, - onClick: (e: React.MouseEvent) => toggleRow(e, row.item.name), + onClick: (e: React.MouseEvent) => toggleRowItem(e, row.item), onKeyDown: (e: React.KeyboardEvent) => { if (e.key === ' ') { e.preventDefault(); - toggleRow(e, row.item.name); + toggleRowItem(e, row.item); } }, selected, @@ -247,13 +281,9 @@ export const KnowledgeList = ({ }; }); - const allRowsSelected = useMemo(() => { - return !rows?.filter((row) => !row.selected)?.length; - }, [rows]); - const toggleAllRows = useCallback(() => { - setSelectedArtifactItems(allRowsSelected ? [] : items.map((item) => item.name)); - }, [setSelectedArtifactItems, items, allRowsSelected]); + setSelectedArtifactItems(allRowsSelected ? [] : Object.values(allItems).map((item) => item.id)); + }, [setSelectedArtifactItems, allItems, allRowsSelected]); const toggleAllKeydown = useCallback( (e: React.KeyboardEvent) => { @@ -268,11 +298,11 @@ export const KnowledgeList = ({ const handleExpandCollapse = useCallback((item: KnowledgeHubItem, event: React.MouseEvent) => { event.stopPropagation(); event.preventDefault(); - setHubItems((prev) => ({ + setAllItems((prev) => ({ ...prev, - [item.name]: { - ...prev[item.name], - isExpanded: !prev[item.name].isExpanded, + [item.id]: { + ...prev[item.id], + isExpanded: !prev[item.id].isExpanded, }, })); }, []); @@ -349,7 +379,7 @@ export const KnowledgeList = ({ const className = isHubGroup ? undefined : styles.artifactNameCell; return ( - +
{isHubGroup ? (