From 133503634d89909123e6d80a5175a8f9e0b777a8 Mon Sep 17 00:00:00 2001 From: Carlos Castro Date: Thu, 16 Jul 2026 14:09:16 -0400 Subject: [PATCH 1/2] perf(designer): Reduce panel rerenders Stabilize panel selector fallbacks and remove per-render selector construction. Fetch independent connection resources concurrently and add regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 78b1618e-11be-48d0-984e-6709297a646e --- Localize/lang/strings.json | 52 -------- .../core/actions/bjsworkflow/connections.ts | 6 +- .../__test__/panelSelectors.perf.spec.tsx | 121 ++++++++++++++++++ .../lib/core/state/panel/panelSelectors.ts | 109 ++++++++-------- .../__test__/panelSelectors.perf.spec.tsx | 77 +++++++++++ .../lib/core/state/panel/panelSelectors.ts | 76 +++++------ 6 files changed, 287 insertions(+), 154 deletions(-) create mode 100644 libs/designer-v2/src/lib/core/state/__test__/panelSelectors.perf.spec.tsx create mode 100644 libs/designer/src/lib/core/state/panel/__test__/panelSelectors.perf.spec.tsx diff --git a/Localize/lang/strings.json b/Localize/lang/strings.json index 3a382abf132..b8356c3a7a1 100644 --- a/Localize/lang/strings.json +++ b/Localize/lang/strings.json @@ -39,7 +39,6 @@ "+l5XmZ": "Enter a positive integer between {min} and {max}", "+mAJR3": "(UTC+08:00) Kuala Lumpur, Singapore", "+mNJQl": "Create a logic app", - "+nCfrr": "Delete assertion", "+nKZHB": "Can't upload file. Please try again. Error: {errorMessage}", "+oX/2M": "Add actions to define the steps in your workflow", "+oelX4": "Required. The string to examine.", @@ -386,7 +385,6 @@ "5R1r3q": "Create a new connection", "5SAQOb": "Authority", "5Tqzsm": "Categorization", - "5U6Dee": "Action Result", "5Vbd0e": "Select a knowledge hub", "5VhY2X": "Loading models...", "5WTRY8": "Errors", @@ -427,7 +425,6 @@ "6776lH": "Processing...", "67FI5P": "Integration service environment", "68UJHa": "This list shows the new resources to create for your logic app and existing resources if any.", - "69+CIW": "View workflow", "6ASWgB": "Confirm that you want to delete this MCP server group? You can't undo this action.", "6D5fAm": "Trigger", "6DZp5H": "Search", @@ -500,7 +497,6 @@ "7MP7FJ": "How do values resolve at runtime?", "7MbFEx": "Select a connection", "7N+Zcl": "App Service Plan", - "7PTnxD": "New assertion", "7PtWvu": "(UTC-05:00) Eastern Time (US & Canada)", "7QymrD": "Required. The string from which the substring is taken.", "7ScdN6": "Name", @@ -534,7 +530,6 @@ "8+0teU": "Parameter name already exists.", "8+TVCG": "Edit: {selectedOperationSummary}", "8/Vjz3": "Knowledge base", - "805nCJ": "Error Message", "80T6Ut": "The deployment is taking longer than expected. Please check the Azure portal for more details.", "80z7j2": "Key-based authentication", "810OUB": "When triggered by another workflow", @@ -719,7 +714,6 @@ "BYrP8F": "Number", "BYsNzz": "Your template has been unpublished.", "Bewmet": "Array", - "BfGFkk": "Test icon", "Bft/H3": "All the benefits of Stateful, plus the option to build AI agents in your workflow to automate complex tasks.", "BjrVzW": "Resource group", "Bkc/+3": "Retry policy minimum interval is invalid, must match ISO 8601 duration format", @@ -914,7 +908,6 @@ "FTrMxN": "Close", "FUXlzD": "Run ID: {id}", "FUuFlC": "Default", - "FUvA4o": "Must provide a condition expression.", "FXLR5M": "{hours, plural, one {# hour} other {# hours}}", "FYtDP0": "Enter an email address.", "Fcvgvg": "Search", @@ -950,7 +943,6 @@ "GQnN3U": "Explain flow", "GR5+k2": "AI and machine learning capabilities", "GSzT3T": "Refresh", - "GX3fkR": "New assertion", "GXFvm+": "To enable chat client for production, setup authentication.", "GYu5XE": "Failed to load run details", "GYui13": "Continue editing", @@ -993,7 +985,6 @@ "H9pzpO": "Deleted the following hub artifacts:", "HDqP2g": "Required. The key name of the form data value to return.", "HET2nV": "Add a Variable", - "HF2SNx": "Is successful", "HF4xfS": "Enter agent name...", "HFt+tF": "Favorites", "HH970i": "Month", @@ -1171,7 +1162,6 @@ "LBlM+D": "Not specified", "LCRHQ9": "(UTC+12:00) Fiji", "LElaX3": "Next flow suggestion", - "LG7hSo": "Assertions", "LGUiVk": "Public access", "LLJrOT": "Description", "LMB8am": "Creating...", @@ -1190,7 +1180,6 @@ "LZm3ze": "Add a parallel branch", "LaFlFh": "Removed this action", "Ld62T8": "Delete", - "LdBN0m": "Assertion name", "LdITnG": "(UTC-03:00) Cayenne, Fortaleza", "LeR+TX": "Zoom in", "Lft/is": "Add new", @@ -1408,7 +1397,6 @@ "Peg6ZT": "Setting errors", "Pehyf2": "Enter agent instructions...", "PfCJlN": "Workflow functions", - "PhBS5+": "Enter name", "PjBcG3": "(UTC+02:00) Amman", "Pk4cb+": "Tracked properties", "Pnt0Xj": "Explain how to receive files from SFTP server.", @@ -1636,7 +1624,6 @@ "U3iWVd": "Generates an array of integers starting from a certain number", "U4zovj": "Runs {onTime} {onDays}", "U6V60S": "Validation failed :", - "U7UAV0": "Mocked Results Tab", "U82s8v": "Select a subscription, resource group and Logic App instance to find the workflows you want to convert to templates. Your changes apply only to this template and won't affect the original workflows.", "U9SHxw": "Code", "UCNM4L": "To reference a parameter, use the dynamic content list.", @@ -1728,7 +1715,6 @@ "VbMYd8": "Triggers tell your app when to start running. Each workflow needs at least one trigger.", "VchR9d": "Headers", "Vecdzb": "Logic app details", - "VfUtlo": "Save unit test definition", "Vi5TIV": "No warnings found.", "ViOMjt": "Use the chat client to talk to your agent.", "VjvWve": "Microsoft Authored", @@ -2003,7 +1989,6 @@ "_+l5XmZ.comment": "description of maximum waiting runs setting", "_+mAJR3.comment": "Time zone value ", "_+mNJQl.comment": "Create new logic app link", - "_+nCfrr.comment": "Create Assertion Text", "_+nKZHB.comment": "Error message when file upload fails in add files panel", "_+oX/2M.comment": "Description of what Actions are, on a tooltip about Actions", "_+oelX4.comment": "Required string parameter to check if is integer using isInt function", @@ -2350,7 +2335,6 @@ "_5R1r3q.comment": "Button text to create a new connection", "_5SAQOb.comment": "Authority Label Display Name", "_5Tqzsm.comment": "Categorization section title", - "_5U6Dee.comment": "The label for the action result dropdown in the unit test panel.", "_5Vbd0e.comment": "Placeholder text for knowledge hub dropdown", "_5VhY2X.comment": "Placeholder while models load for create form", "_5WTRY8.comment": "The tab label for the errors tab on the errors panel", @@ -2391,7 +2375,6 @@ "_6776lH.comment": "Processing message in the chatbot", "_67FI5P.comment": "ISE divider label", "_68UJHa.comment": "The aria label for the resources table", - "_69+CIW.comment": "View workflow button text", "_6ASWgB.comment": "Content for the delete MCP server group modal", "_6D5fAm.comment": "Tag for trigger operations", "_6DZp5H.comment": "Placeholder text for search connectors", @@ -2464,7 +2447,6 @@ "_7MP7FJ.comment": "Link text for learning more about tool parameters", "_7MbFEx.comment": "Title for step 1 - connection selection", "_7N+Zcl.comment": "The type for app service plan resource", - "_7PTnxD.comment": "Heading title for an assertion without name", "_7PtWvu.comment": "Time zone value ", "_7QymrD.comment": "Required string parameter required to obtain substring", "_7ScdN6.comment": "Deployment model resource name label", @@ -2498,7 +2480,6 @@ "_8+0teU.comment": "Error message when the workflow parameter name already exists.", "_8+TVCG.comment": "Title for edit operation panel", "_8/Vjz3.comment": "Title for knowledge hub editor", - "_805nCJ.comment": "Label for the error message input field", "_80T6Ut.comment": "Error message shown when deployment times out", "_80z7j2.comment": "Description for key-based authentication", "_810OUB.comment": "Workflow execution trigger category", @@ -2683,7 +2664,6 @@ "_BYrP8F.comment": "Placeholder title for a newly inserted Number parameter", "_BYsNzz.comment": "Title for the toaster after unpublishing template.", "_Bewmet.comment": "Title for array dropdown input setting", - "_BfGFkk.comment": "Test icon aria label", "_Bft/H3.comment": "Autonomous agents workflow description", "_BjrVzW.comment": "Label for choosing resource group", "_Bkc/+3.comment": "error message for invalid minimum retry interval", @@ -2878,7 +2858,6 @@ "_FTrMxN.comment": "Button text for closing the panel", "_FUXlzD.comment": "Label for the run ID", "_FUuFlC.comment": "Default, the backup option if none other apply", - "_FUvA4o.comment": "Error message when the assertion condition expression is empty.", "_FXLR5M.comment": "A duration of time shown in hours", "_FYtDP0.comment": "The placeholder description for a newly inserted 'Email' parameter.", "_Fcvgvg.comment": "Default placeholder for search box that searches dropdown options", @@ -2914,7 +2893,6 @@ "_GQnN3U.comment": "Chatbot prompt to explain the flow", "_GR5+k2.comment": "AI Agent category description", "_GSzT3T.comment": "Refresh button text to reload agent details", - "_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", @@ -2957,7 +2935,6 @@ "_H9pzpO.comment": "Content for the toaster after successfully deleting hub artifacts, with the names of the deleted artifacts", "_HDqP2g.comment": "Required string parameter to be used as key for triggerFormDataValue function", "_HET2nV.comment": "label to add a variable", - "_HF2SNx.comment": "Successful action result", "_HF4xfS.comment": "Placeholder for agent name input", "_HFt+tF.comment": "Favorites category", "_HH970i.comment": "Frequency value ", @@ -3135,7 +3112,6 @@ "_LBlM+D.comment": "The status message to show not specified in monitoring view.", "_LCRHQ9.comment": "Time zone value ", "_LElaX3.comment": "Text for button that shows the next flow suggestion", - "_LG7hSo.comment": "Unit test assertions button", "_LGUiVk.comment": "Label for the public access field", "_LLJrOT.comment": "Label for the operation description field", "_LMB8am.comment": "Button text to show a connection is being created", @@ -3154,7 +3130,6 @@ "_LZm3ze.comment": "Text for button to add a parallel branch", "_LaFlFh.comment": "Chatbot removed operation sentence format", "_Ld62T8.comment": "Button text for deleting selected workflows", - "_LdBN0m.comment": "Assertion field name title", "_LdITnG.comment": "Time zone value ", "_LeR+TX.comment": "Aria label for a button that zooms in on the workflow", "_Lft/is.comment": "Button to add a new connection", @@ -3372,7 +3347,6 @@ "_Peg6ZT.comment": "Header for the setting errors subsection", "_Pehyf2.comment": "Placeholder for instructions textarea", "_PfCJlN.comment": "Label for workflow functions", - "_PhBS5+.comment": "Assertion field name placeholder", "_PjBcG3.comment": "Time zone value ", "_Pk4cb+.comment": "title for tracked properties setting", "_Pnt0Xj.comment": "Chatbot suggestion message to recieve specific files from SFTP server", @@ -3600,7 +3574,6 @@ "_U3iWVd.comment": "Label for description of custom range Function", "_U4zovj.comment": "Recurrence schedule description on days of week at times", "_U6V60S.comment": "Error message title for workflow validation errors", - "_U7UAV0.comment": "An accessibility label that describes the mocked results tab", "_U82s8v.comment": "Label for the logic app resource selection description", "_U9SHxw.comment": "Code view title", "_UCNM4L.comment": "Description for Workflow Parameters Part 2", @@ -3692,7 +3665,6 @@ "_VbMYd8.comment": "Description of what Triggers are, on a tooltip about Triggers", "_VchR9d.comment": "Headers", "_Vecdzb.comment": "Logic app details step title", - "_VfUtlo.comment": "Save unit test button", "_Vi5TIV.comment": "Text to show when no warnings exist", "_ViOMjt.comment": "Option 2 description when auth is enabled", "_VjvWve.comment": "Label text for Microsoft authored templates tab", @@ -3950,7 +3922,6 @@ "_aSnCCB.comment": "Header for resource lcoation", "_aV/nLS.comment": "Frequency value ", "_aWcxdZ.comment": "Label text custom templates tab", - "_aWkG01.comment": "Unsupported message for mock results tab", "_aYTy7X.comment": "The status message to show in monitoring view.", "_aZtqSZ.comment": "Default error message for deployment model resource creation", "_afCjXx.comment": "Custom time range start label", @@ -4079,7 +4050,6 @@ "_cwHxwb.comment": "Text for create connection button", "_cx1uqu.comment": "Label for wrapping selected actions in a scope", "_cyEBus.comment": "Time zone value ", - "_cySYfO.comment": "Failed action result", "_d020eg.comment": "Label for description of custom first Function", "_d0BKdI.comment": "Message displayed when search returns no results", "_d0wgcV.comment": "Indicates that the run is currently waiting", @@ -4130,7 +4100,6 @@ "_dsz+Ae.comment": "Label for description of custom split Function", "_duhwio.comment": "Label for the API key", "_dwrqEc.comment": "Warnings section title", - "_dzPAxq.comment": "Edit Assertion Text", "_e+GuGo.comment": "Placeholder title for a newly inserted Text parameter", "_e00zot.comment": "Recurrence parameter group title", "_e1+Gqi.comment": "Description for resource location section.", @@ -4256,7 +4225,6 @@ "_gWNQQQ.comment": "Title for the resource selection section", "_gWyYg0.comment": "Time zone value ", "_gYaVvl.comment": "Error validation message for floats", - "_gZRdSs.comment": "Label for the error code input field", "_gaHI0k.comment": "Message to show when 0 or more than 2 items are present in the overflow menu", "_gc6v4X.comment": "Text for system assigned managed identity", "_gcn3Jg.comment": "Placeholder for the group description input field", @@ -4316,7 +4284,6 @@ "_hRVVdR.comment": "Chatbot undo button for undoing assistant change to flow", "_hTjAB+.comment": "Error message when tool name exceeds maximum characters", "_hUnLzB.comment": "Chatbot progress message shown while generating the final workflow from connector results", - "_hW7oe7.comment": "Assertion field description placeholder", "_hZqQdt.comment": "Time zone value ", "_haeWoU.comment": "Error message when splitOn cannot be evaluated", "_hbOvB4.comment": "Dislike button text for suggested flow", @@ -4469,7 +4436,6 @@ "_kM+Mr0.comment": "Loading text when items are being fetched", "_kN6kce.comment": "Label for description of custom decodeBase64 Function", "_kSK9Pq.comment": "Time zone value ", - "_kSXjTx.comment": "Assertion field no description text", "_kU4VfD.comment": "Choice group first choice: Stateful Type", "_kVwJXt.comment": "Time zone value ", "_kXn5e0.comment": "Chabot input placeholder text", @@ -4498,7 +4464,6 @@ "_kxv92S.comment": "Confirmation message for deleting a server", "_l/3yJr.comment": "Text to show when there is an error with the connection", "_l/9YHQ.comment": "Time zone value ", - "_l0hO5f.comment": "Error message when the workflow assertion name already exists.", "_l2YXln.comment": "Chatbot prompt to replace an action", "_l36V56.comment": "This is a time duration in full non abbreviated format", "_l3nn+9.comment": "Required field label", @@ -4511,7 +4476,6 @@ "_l9TY/4.comment": "ARIA label and tooltip text for the popup button", "_lA/sHA.comment": "Accessibility label for a button to copy all text in a value box", "_lB56l2.comment": "Error validation message for Numbers", - "_lC+EbT.comment": "The tab label for the mocked results tab on the operation panel", "_lFWXhc.comment": "The tab label for the monitoring parameters tab on the operation panel", "_lIVS+K.comment": "Name of the organization or developer that published this template", "_lK+Vzo.comment": "This is an option in a dropdown where users can select type Secure String for their parameter.", @@ -4679,7 +4643,6 @@ "_oBK3A4.comment": "Accessible label for editable expression token", "_oChTO9.comment": "Accessibility label for the select workflow row checkbox", "_oDHXKh.comment": "Display name for item output", - "_oFq3ng.comment": "Assertions Panel Title", "_oGINHJ.comment": "Workflow version filter label", "_oIRKrF.comment": "Text to show no connections present in the template.", "_oJebOR.comment": "Button text displayed while saving authentication settings", @@ -4863,7 +4826,6 @@ "_s0Qaot.comment": "Workflow Parameters Panel", "_s2f0XK.comment": "Content for the toaster after creating a group in add files panel", "_s2ydQX.comment": "Error validation message for invalid JSON object. Do not remove the double single quotes around the display name, as it is needed to wrap the placeholder text.", - "_s4omwa.comment": "Assertion field description title", "_s5AOpV.comment": "Label for the title for panel header card", "_s5RV9B.comment": "Label for description of custom dayOfMonth Function", "_s5jHO7.comment": "Label for agent name input", @@ -4948,7 +4910,6 @@ "_tEqgJQ.comment": "Delete button disabled tooltip text", "_tGSsgZ.comment": "Placehodler text for dropdown", "_tH2pT1.comment": "Text for the Details page navigation heading", - "_tHDcfJ.comment": "Error message when the assertion name is empty.", "_tHFp5l.comment": "Title for the main section with the count of tools", "_tIl0ss.comment": "Title for YouTube video iframe", "_tImHz/.comment": "This is a time duration in abbreviated format", @@ -5033,7 +4994,6 @@ "_uh/+ZN.comment": "Time zone value ", "_ui3KuP.comment": "Header for the input errors category", "_ui7GCl.comment": "Hour of the day", - "_ujp53j.comment": "Assertion field condition title", "_ukB9Bs.comment": "Setup section description in info dialog", "_ukGRNP.comment": "Message shown when parallel branch addition is disabled on root in A2A workflows", "_umLmPm.comment": "Operation Panel", @@ -5330,7 +5290,6 @@ "aSnCCB": "Location", "aV/nLS": "Second", "aWcxdZ": "My Templates", - "aWkG01": "This operation does not support mocking. Mocking is only supported for operations that are connected to a service provider, function, API connection, or API Management.", "aYTy7X": "Cancelled", "aZtqSZ": "An error occurred while creating the deployment model resource.", "afCjXx": "Start", @@ -5459,7 +5418,6 @@ "cwHxwb": "Add connection", "cx1uqu": "Scope", "cyEBus": "(UTC-04:00) Cuiaba", - "cySYfO": "Has failed", "d020eg": "Returns the first element from the passed-in array or string.", "d0BKdI": "Search can't find any results.", "d0wgcV": "Waiting", @@ -5510,7 +5468,6 @@ "dsz+Ae": "Splits the string using a separator", "duhwio": "API key", "dwrqEc": "Warnings", - "dzPAxq": "Edit assertion", "e+GuGo": "Input", "e00zot": "How often do you want to check for items?", "e1+Gqi": "Select the resource location for your workflow", @@ -5636,7 +5593,6 @@ "gWNQQQ": "Project details", "gWyYg0": "(UTC+05:00) Ashgabat, Tashkent", "gYaVvl": "Enter a valid float.", - "gZRdSs": "Error Code", "gaHI0k": "{overflowItemsLength} more items", "gc6v4X": "System-assigned managed identity", "gcn3Jg": "Enter a description", @@ -5696,7 +5652,6 @@ "hRVVdR": "Undo", "hTjAB+": "Tool name cannot exceed maximum characters", "hUnLzB": "Building workflow...", - "hW7oe7": "Enter description", "hZqQdt": "(UTC+02:00) Gaza, Hebron", "haeWoU": "Failed to evaluate outputs because splitOn {splitOn} cannot be evaluated. As a result, this operation's outputs might not be correctly visible in subsequent actions", "hbOvB4": "This isn't what I'm looking for", @@ -5849,7 +5804,6 @@ "kM+Mr0": "Loading...", "kN6kce": "Returns a string representation of an input based64 string", "kSK9Pq": "(UTC-05:00) Bogota, Lima, Quito, Rio Branco", - "kSXjTx": "No description", "kU4VfD": "Stateful", "kVwJXt": "(UTC+04:00) Abu Dhabi, Muscat", "kXn5e0": "Ask a question about this workflow or about Azure Logic Apps as a whole ...", @@ -5878,7 +5832,6 @@ "kxv92S": "Successfully deleted MCP server: ''{serverName}''.", "l/3yJr": "Invalid connection", "l/9YHQ": "(UTC+01:00) Windhoek", - "l0hO5f": "Assertion name already exists.", "l2YXln": "Replace action with", "l36V56": "{hours} hours {minutes} minutes", "l3nn+9": "Required field", @@ -5891,7 +5844,6 @@ "l9TY/4": "Open in popup", "lA/sHA": "Copy the value of ''{label}'' to the clipboard", "lB56l2": "Enter a valid number.", - "lC+EbT": "Mocked Results", "lFWXhc": "Workflow", "lIVS+K": "By", "lK+Vzo": "Secure string", @@ -6059,7 +6011,6 @@ "oBK3A4": "Edit {tokenTitle} expression", "oChTO9": "Select workflow row checkbox label", "oDHXKh": "Item", - "oFq3ng": "Assertions", "oGINHJ": "Version", "oIRKrF": "No connections in this template", "oJebOR": "Saving...", @@ -6243,7 +6194,6 @@ "s0Qaot": "Workflow Parameters Panel", "s2f0XK": "Group {name} has been created and selected.", "s2ydQX": "''Value'' must be a valid JSON object", - "s4omwa": "Description", "s5AOpV": "Card title", "s5RV9B": "Returns the day of year component of a string timestamp", "s5jHO7": "Agent name", @@ -6328,7 +6278,6 @@ "tEqgJQ": "Cannot delete the last parameter", "tGSsgZ": "select an identity", "tH2pT1": "Return to search", - "tHDcfJ": "Must provide the assertion name.", "tHFp5l": "Tools", "tIl0ss": "YouTube video player", "tImHz/": "{days}d {hours}h", @@ -6413,7 +6362,6 @@ "uh/+ZN": "(UTC-03:00) Greenland", "ui3KuP": "Operation errors", "ui7GCl": "16", - "ujp53j": "Condition expression", "ukB9Bs": "To enable authentication for production use, follow the authentication setup guide.", "ukGRNP": "Cannot add parallel branches on the root level in agent to agent workflows", "umLmPm": "Operation Panel", diff --git a/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts b/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts index 48bd20744d2..72599286357 100644 --- a/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts +++ b/libs/designer-v2/src/lib/core/actions/bjsworkflow/connections.ts @@ -414,8 +414,10 @@ export const updateIdentityChangeInConnection = createAsyncThunk( api: { id: connectorId }, connection: { id: connectionId }, } = getConnectionReference(rootState.connections, nodeId); - const connector = await getConnector(connectorId); - const connection = await getConnection(connectionId, connectorId); + // `getConnector` and `getConnection` are independent reads (getConnection does not + // consume the connector result), so fetch them concurrently instead of waterfalling + // the two network round-trips. + const [connector, connection] = await Promise.all([getConnector(connectorId), getConnection(connectionId, connectorId)]); await ConnectionService().setupConnectionIfNeeded(connection as Connection, userAssignedIdentity); diff --git a/libs/designer-v2/src/lib/core/state/__test__/panelSelectors.perf.spec.tsx b/libs/designer-v2/src/lib/core/state/__test__/panelSelectors.perf.spec.tsx new file mode 100644 index 00000000000..63c89893a99 --- /dev/null +++ b/libs/designer-v2/src/lib/core/state/__test__/panelSelectors.perf.spec.tsx @@ -0,0 +1,121 @@ +/** + * @vitest-environment jsdom + * + * Regression coverage for the panel selector referential-stability fix. + * + * The previous implementation wrapped every panel hook in a per-render `createSelector` + * and fell back to freshly-allocated `[]` / `{}` values. On any state where the underlying + * value was `undefined` (a path the selectors explicitly guard with `?.` / `??`), each + * store update produced a brand new reference, so react-redux's default `===` comparison + * failed and consumers re-rendered on every unrelated dispatch. + * + * These tests capture both the "before" behavior (reproduced inline) and the "after" + * behavior of the shipped selectors so the improvement is measurable and protected. + */ +import { describe, expect, it } from 'vitest'; +import { render, act } from '@testing-library/react'; +import { Provider, useSelector } from 'react-redux'; +import { configureStore, createSelector } from '@reduxjs/toolkit'; +import panelReducer, { initialState, setIsPanelLoading } from '../panel/panelSlice'; +import { useOperationAlternateSelectedNode, useOperationPanelSelectedNodeIds } from '../panel/panelSelectors'; + +// Store whose operationContent selection fields are `undefined`, exercising the fallback +// branches of the selectors. This mirrors host-provided / rehydrated panel state where +// these optional fields are absent (hence the `?.` / `??` guards in the selectors). +const makeStoreWithUndefinedSelection = () => + configureStore({ + reducer: { panel: panelReducer }, + preloadedState: { + panel: { + ...initialState, + operationContent: { + ...initialState.operationContent, + selectedNodeIds: undefined, + alternateSelectedNode: undefined, + }, + }, + } as any, + }); + +// Faithful reproduction of the previous (buggy) implementation, used purely to document +// the "before" behavior in a runnable form. +const getPanelState = (state: any) => state.panel; +const oldUseOperationPanelSelectedNodeIds = () => + useSelector(createSelector(getPanelState, (state: any) => state.operationContent?.selectedNodeIds ?? [])); +const oldUseOperationAlternateSelectedNode = () => + useSelector(createSelector(getPanelState, (state: any) => state.operationContent.alternateSelectedNode ?? {})); + +const mountConsumer = (useHook: () => unknown) => { + const stats = { renders: 0, value: undefined as unknown }; + const Consumer = () => { + stats.value = useHook(); + stats.renders += 1; + return null; + }; + const store = makeStoreWithUndefinedSelection(); + render( + + + + ); + return { store, stats }; +}; + +describe('panelSelectors referential stability (perf regression)', () => { + describe('useOperationPanelSelectedNodeIds', () => { + it('BEFORE: createSelector-in-hook returned a new [] and forced a re-render on an unrelated dispatch', () => { + const { store, stats } = mountConsumer(oldUseOperationPanelSelectedNodeIds); + const firstRenders = stats.renders; + const firstValue = stats.value; + + act(() => { + store.dispatch(setIsPanelLoading(true)); + }); + + expect(stats.renders).toBeGreaterThan(firstRenders); // spurious re-render + expect(stats.value).not.toBe(firstValue); // fresh [] reference each dispatch + }); + + it('AFTER: keeps a stable [] reference and does not re-render on an unrelated dispatch', () => { + const { store, stats } = mountConsumer(useOperationPanelSelectedNodeIds); + const firstRenders = stats.renders; + const firstValue = stats.value; + expect(firstValue).toEqual([]); + + act(() => { + store.dispatch(setIsPanelLoading(true)); + }); + + expect(stats.renders).toBe(firstRenders); // no spurious re-render + expect(stats.value).toBe(firstValue); // stable reference + }); + }); + + describe('useOperationAlternateSelectedNode', () => { + it('BEFORE: createSelector-in-hook returned a new {} and forced a re-render on an unrelated dispatch', () => { + const { store, stats } = mountConsumer(oldUseOperationAlternateSelectedNode); + const firstRenders = stats.renders; + const firstValue = stats.value; + + act(() => { + store.dispatch(setIsPanelLoading(true)); + }); + + expect(stats.renders).toBeGreaterThan(firstRenders); + expect(stats.value).not.toBe(firstValue); + }); + + it('AFTER: keeps a stable {} reference and does not re-render on an unrelated dispatch', () => { + const { store, stats } = mountConsumer(useOperationAlternateSelectedNode); + const firstRenders = stats.renders; + const firstValue = stats.value; + + act(() => { + store.dispatch(setIsPanelLoading(true)); + }); + + expect(stats.renders).toBe(firstRenders); + expect(stats.value).toBe(firstValue); + }); + }); +}); diff --git a/libs/designer-v2/src/lib/core/state/panel/panelSelectors.ts b/libs/designer-v2/src/lib/core/state/panel/panelSelectors.ts index f2d2638fa0a..b098c51610a 100644 --- a/libs/designer-v2/src/lib/core/state/panel/panelSelectors.ts +++ b/libs/designer-v2/src/lib/core/state/panel/panelSelectors.ts @@ -1,71 +1,66 @@ import type { RootState } from '../../store'; import { canWrapSelectedNodes } from '../../utils/multiselect'; -import { createSelector } from '@reduxjs/toolkit'; +import type { OperationPanelContentState } from './panelTypes'; import { useSelector } from 'react-redux'; -const getPanelState = (state: RootState) => state.panel; +// Stable empty references shared by selectors that fall back to an empty value. +// Returning a freshly-allocated `[]` / `{}` from a selector makes react-redux's +// default `===` comparison fail on every dispatch, forcing consumers to re-render +// even when nothing they read has actually changed. Reusing a module-level singleton +// keeps the reference stable so those consumers only re-render when their data changes. +const emptySelectedNodeIds: string[] = []; +const emptyAlternateSelectedNode: NonNullable = {}; -export const useConnectionPanelSelectedNodeIds = () => - useSelector(createSelector(getPanelState, (state) => state.connectionContent.selectedNodeIds)); +export const useConnectionPanelSelectedNodeIds = () => useSelector((state: RootState) => state.panel.connectionContent.selectedNodeIds); -export const useCurrentPanelMode = () => useSelector(createSelector(getPanelState, (state) => state.currentPanelMode)); +export const useCurrentPanelMode = () => useSelector((state: RootState) => state.panel.currentPanelMode); export const useDiscoveryPanelSelectedOperationGroupId = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectedOperationGroupId)); + useSelector((state: RootState) => state.panel.discoveryContent.selectedOperationGroupId); export const useDiscoveryPanelSelectedOperationId = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectedOperationId)); + useSelector((state: RootState) => state.panel.discoveryContent.selectedOperationId); -export const useDiscoveryPanelSelectedNodeIds = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectedNodeIds)); +export const useDiscoveryPanelSelectedNodeIds = () => useSelector((state: RootState) => state.panel.discoveryContent.selectedNodeIds); -export const useDiscoveryPanelIsAddingTrigger = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.isAddingTrigger)); +export const useDiscoveryPanelIsAddingTrigger = () => useSelector((state: RootState) => state.panel.discoveryContent.isAddingTrigger); -export const useDiscoveryPanelIsParallelBranch = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.isParallelBranch)); +export const useDiscoveryPanelIsParallelBranch = () => useSelector((state: RootState) => state.panel.discoveryContent.isParallelBranch); -export const useDiscoveryPanelRelationshipIds = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.relationshipIds)); +export const useDiscoveryPanelRelationshipIds = () => useSelector((state: RootState) => state.panel.discoveryContent.relationshipIds); -export const useDiscoveryPanelFavoriteOperations = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.favoriteOperations)); +export const useDiscoveryPanelFavoriteOperations = () => useSelector((state: RootState) => state.panel.discoveryContent.favoriteOperations); export const useDiscoveryPanelSelectedBrowseCategory = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectedBrowseCategory)); + useSelector((state: RootState) => state.panel.discoveryContent.selectedBrowseCategory); -export const useDiscoveryPanelSelectionState = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectionState)); +export const useDiscoveryPanelSelectionState = () => useSelector((state: RootState) => state.panel.discoveryContent.selectionState); export const useDiscoveryPanelIsOperationFavorited = (connectorId: string, operationId?: string) => useDiscoveryPanelFavoriteOperations().some((favorite) => favorite.connectorId === connectorId && favorite.operationId === operationId); -export const useErrorsPanelSelectedTabId = () => useSelector(createSelector(getPanelState, (state) => state.errorContent.selectedTabId)); +export const useErrorsPanelSelectedTabId = () => useSelector((state: RootState) => state.panel.errorContent.selectedTabId); -export const useFocusReturnElementId = () => useSelector(createSelector(getPanelState, (state) => state.focusReturnElementId)); +export const useFocusReturnElementId = () => useSelector((state: RootState) => state.panel.focusReturnElementId); -export const useIsCreatingConnection = () => - useSelector(createSelector(getPanelState, (state) => state.connectionContent.isCreatingConnection)); +export const useIsCreatingConnection = () => useSelector((state: RootState) => state.panel.connectionContent.isCreatingConnection); -export const useIsPanelCollapsed = () => useSelector(createSelector(getPanelState, (state) => state.isCollapsed)); +export const useIsPanelCollapsed = () => useSelector((state: RootState) => state.panel.isCollapsed); -export const useIsPanelLoading = () => useSelector(createSelector(getPanelState, (state) => state.isLoading)); +export const useIsPanelLoading = () => useSelector((state: RootState) => state.panel.isLoading); export const useIsNodePinnedToOperationPanel = (nodeId: string) => useSelector( - createSelector(getPanelState, (state) => { - return ( - (state.operationContent.alternateSelectedNode?.nodeId ?? '') === nodeId && - (state.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned' - ); - }) + (state: RootState) => + (state.panel.operationContent.alternateSelectedNode?.nodeId ?? '') === nodeId && + (state.panel.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned' ); export const useIsAlternateNodePinned = () => - useSelector(createSelector(getPanelState, (state) => (state.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned')); + useSelector((state: RootState) => (state.panel.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned'); export const useIsNodeSelectedInOperationPanel = (nodeId: string) => - useSelector(createSelector(getPanelState, (state) => (state.operationContent.selectedNodeId ?? '') === nodeId)); + useSelector((state: RootState) => (state.panel.operationContent.selectedNodeId ?? '') === nodeId); export const useIsPanelInPinnedViewMode = (): boolean => { const selectedNodeId = useOperationPanelSelectedNodeId(); @@ -74,52 +69,48 @@ export const useIsPanelInPinnedViewMode = (): boolean => { }; export const useOperationAlternateSelectedNodeId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.alternateSelectedNode?.nodeId ?? '')); + useSelector((state: RootState) => state.panel.operationContent.alternateSelectedNode?.nodeId ?? ''); export const useOperationAlternateSelectedNode = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.alternateSelectedNode ?? {})); + useSelector((state: RootState) => state.panel.operationContent.alternateSelectedNode ?? emptyAlternateSelectedNode); export const useOperationPanelAlternateNodeActiveTabId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.alternateSelectedNode?.activeTabId)); + useSelector((state: RootState) => state.panel.operationContent.alternateSelectedNode?.activeTabId); -export const useOperationPanelSelectedNodeId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent?.selectedNodeId ?? '')); +export const useOperationPanelSelectedNodeId = () => useSelector((state: RootState) => state.panel.operationContent?.selectedNodeId ?? ''); export const useOperationPanelSelectedNodeIds = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent?.selectedNodeIds ?? [])); + useSelector((state: RootState) => state.panel.operationContent?.selectedNodeIds ?? emptySelectedNodeIds); export const useCanWrapSelectedNodes = () => - useSelector((state: RootState) => canWrapSelectedNodes(state.workflow, state.panel.operationContent?.selectedNodeIds ?? [])); + useSelector((state: RootState) => + canWrapSelectedNodes(state.workflow, state.panel.operationContent?.selectedNodeIds ?? emptySelectedNodeIds) + ); export const useIsNodeInMultiSelection = (nodeId: string) => - useSelector(createSelector(getPanelState, (state) => (state.operationContent?.selectedNodeIds ?? []).includes(nodeId))); + useSelector((state: RootState) => (state.panel.operationContent?.selectedNodeIds ?? emptySelectedNodeIds).includes(nodeId)); export const useOperationPanelSelectedNodeActiveTabId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.selectedNodeActiveTabId)); + useSelector((state: RootState) => state.panel.operationContent.selectedNodeActiveTabId); -export const usePanelLocation = () => useSelector(createSelector(getPanelState, (state) => state.location)); +export const usePanelLocation = () => useSelector((state: RootState) => state.panel.location); -export const usePreviousPanelMode = () => useSelector(createSelector(getPanelState, (state) => state.previousPanelMode)); +export const usePreviousPanelMode = () => useSelector((state: RootState) => state.panel.previousPanelMode); -export const useIsAddingAgentTool = () => useSelector(createSelector(getPanelState, (state) => state.discoveryContent.isAddingAgentTool)); +export const useIsAddingAgentTool = () => useSelector((state: RootState) => state.panel.discoveryContent.isAddingAgentTool); -export const useDiscoveryPanelSearchTerm = () => useSelector(createSelector(getPanelState, (state) => state.discoveryContent.searchTerm)); +export const useDiscoveryPanelSearchTerm = () => useSelector((state: RootState) => state.panel.discoveryContent.searchTerm); -export const useIsRunHistoryCollapsed = () => useSelector(createSelector(getPanelState, (state) => state.runHistoryCollapsed)); +export const useIsRunHistoryCollapsed = () => useSelector((state: RootState) => state.panel.runHistoryCollapsed); -export const useMcpToolWizard = () => useSelector(createSelector(getPanelState, (state) => state.discoveryContent.mcpToolWizard)); +export const useMcpToolWizard = () => useSelector((state: RootState) => state.panel.discoveryContent.mcpToolWizard); -export const useMcpWizardStep = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.mcpToolWizard?.currentStep)); +export const useMcpWizardStep = () => useSelector((state: RootState) => state.panel.discoveryContent.mcpToolWizard?.currentStep); -export const useMcpWizardOperation = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.mcpToolWizard?.operation)); +export const useMcpWizardOperation = () => useSelector((state: RootState) => state.panel.discoveryContent.mcpToolWizard?.operation); -export const useMcpWizardConnectionId = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.mcpToolWizard?.connectionId)); +export const useMcpWizardConnectionId = () => useSelector((state: RootState) => state.panel.discoveryContent.mcpToolWizard?.connectionId); -export const useMcpWizardAllowedTools = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.mcpToolWizard?.allowedTools)); +export const useMcpWizardAllowedTools = () => useSelector((state: RootState) => state.panel.discoveryContent.mcpToolWizard?.allowedTools); -export const useMcpWizardHeaders = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.mcpToolWizard?.headers)); +export const useMcpWizardHeaders = () => useSelector((state: RootState) => state.panel.discoveryContent.mcpToolWizard?.headers); diff --git a/libs/designer/src/lib/core/state/panel/__test__/panelSelectors.perf.spec.tsx b/libs/designer/src/lib/core/state/panel/__test__/panelSelectors.perf.spec.tsx new file mode 100644 index 00000000000..673f1402b4a --- /dev/null +++ b/libs/designer/src/lib/core/state/panel/__test__/panelSelectors.perf.spec.tsx @@ -0,0 +1,77 @@ +/** + * @vitest-environment jsdom + * + * Regression coverage for the panel selector referential-stability fix (designer v1). + * See the designer-v2 counterpart for the full rationale. + */ +import { describe, expect, it } from 'vitest'; +import { render, act } from '@testing-library/react'; +import { Provider, useSelector } from 'react-redux'; +import { configureStore, createSelector } from '@reduxjs/toolkit'; +import panelReducer, { initialState, setIsPanelLoading } from '../panelSlice'; +import { useOperationAlternateSelectedNode } from '../panelSelectors'; + +const makeStoreWithUndefinedSelection = () => + configureStore({ + reducer: { panel: panelReducer }, + preloadedState: { + panel: { + ...initialState, + operationContent: { + ...initialState.operationContent, + alternateSelectedNode: undefined, + }, + }, + } as any, + }); + +// Faithful reproduction of the previous (buggy) implementation. +const getPanelState = (state: any) => state.panel; +const oldUseOperationAlternateSelectedNode = () => + useSelector(createSelector(getPanelState, (state: any) => state.operationContent.alternateSelectedNode ?? {})); + +const mountConsumer = (useHook: () => unknown) => { + const stats = { renders: 0, value: undefined as unknown }; + const Consumer = () => { + stats.value = useHook(); + stats.renders += 1; + return null; + }; + const store = makeStoreWithUndefinedSelection(); + render( + + + + ); + return { store, stats }; +}; + +describe('panelSelectors referential stability (perf regression)', () => { + describe('useOperationAlternateSelectedNode', () => { + it('BEFORE: createSelector-in-hook returned a new {} and forced a re-render on an unrelated dispatch', () => { + const { store, stats } = mountConsumer(oldUseOperationAlternateSelectedNode); + const firstRenders = stats.renders; + const firstValue = stats.value; + + act(() => { + store.dispatch(setIsPanelLoading(true)); + }); + + expect(stats.renders).toBeGreaterThan(firstRenders); + expect(stats.value).not.toBe(firstValue); + }); + + it('AFTER: keeps a stable {} reference and does not re-render on an unrelated dispatch', () => { + const { store, stats } = mountConsumer(useOperationAlternateSelectedNode); + const firstRenders = stats.renders; + const firstValue = stats.value; + + act(() => { + store.dispatch(setIsPanelLoading(true)); + }); + + expect(stats.renders).toBe(firstRenders); + expect(stats.value).toBe(firstValue); + }); + }); +}); diff --git a/libs/designer/src/lib/core/state/panel/panelSelectors.ts b/libs/designer/src/lib/core/state/panel/panelSelectors.ts index ac22aa373c6..cd53fd83864 100644 --- a/libs/designer/src/lib/core/state/panel/panelSelectors.ts +++ b/libs/designer/src/lib/core/state/panel/panelSelectors.ts @@ -1,64 +1,59 @@ import type { RootState } from '../../store'; -import { createSelector } from '@reduxjs/toolkit'; +import type { OperationPanelContentState } from './panelTypes'; import { useSelector } from 'react-redux'; -const getPanelState = (state: RootState) => state.panel; +// Stable empty reference shared by selectors that fall back to an empty value. +// Returning a freshly-allocated `{}` from a selector makes react-redux's default +// `===` comparison fail on every dispatch, forcing consumers to re-render even when +// nothing they read has actually changed. Reusing a module-level singleton keeps the +// reference stable so those consumers only re-render when their data changes. +const emptyAlternateSelectedNode: NonNullable = {}; -export const useConnectionPanelSelectedNodeIds = () => - useSelector(createSelector(getPanelState, (state) => state.connectionContent.selectedNodeIds)); +export const useConnectionPanelSelectedNodeIds = () => useSelector((state: RootState) => state.panel.connectionContent.selectedNodeIds); -export const useCurrentPanelMode = () => useSelector(createSelector(getPanelState, (state) => state.currentPanelMode)); +export const useCurrentPanelMode = () => useSelector((state: RootState) => state.panel.currentPanelMode); export const useDiscoveryPanelSelectedOperationGroupId = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectedOperationGroupId)); + useSelector((state: RootState) => state.panel.discoveryContent.selectedOperationGroupId); export const useDiscoveryPanelSelectedOperationId = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectedOperationId)); + useSelector((state: RootState) => state.panel.discoveryContent.selectedOperationId); -export const useDiscoveryPanelSelectedNodeIds = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.selectedNodeIds)); +export const useDiscoveryPanelSelectedNodeIds = () => useSelector((state: RootState) => state.panel.discoveryContent.selectedNodeIds); -export const useDiscoveryPanelIsAddingTrigger = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.isAddingTrigger)); +export const useDiscoveryPanelIsAddingTrigger = () => useSelector((state: RootState) => state.panel.discoveryContent.isAddingTrigger); -export const useDiscoveryPanelIsParallelBranch = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.isParallelBranch)); +export const useDiscoveryPanelIsParallelBranch = () => useSelector((state: RootState) => state.panel.discoveryContent.isParallelBranch); -export const useDiscoveryPanelRelationshipIds = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.relationshipIds)); +export const useDiscoveryPanelRelationshipIds = () => useSelector((state: RootState) => state.panel.discoveryContent.relationshipIds); -export const useDiscoveryPanelFavoriteOperations = () => - useSelector(createSelector(getPanelState, (state) => state.discoveryContent.favoriteOperations)); +export const useDiscoveryPanelFavoriteOperations = () => useSelector((state: RootState) => state.panel.discoveryContent.favoriteOperations); export const useDiscoveryPanelIsOperationFavorited = (connectorId: string, operationId?: string) => useDiscoveryPanelFavoriteOperations().some((favorite) => favorite.connectorId === connectorId && favorite.operationId === operationId); -export const useErrorsPanelSelectedTabId = () => useSelector(createSelector(getPanelState, (state) => state.errorContent.selectedTabId)); +export const useErrorsPanelSelectedTabId = () => useSelector((state: RootState) => state.panel.errorContent.selectedTabId); -export const useFocusReturnElementId = () => useSelector(createSelector(getPanelState, (state) => state.focusReturnElementId)); +export const useFocusReturnElementId = () => useSelector((state: RootState) => state.panel.focusReturnElementId); -export const useIsCreatingConnection = () => - useSelector(createSelector(getPanelState, (state) => state.connectionContent.isCreatingConnection)); +export const useIsCreatingConnection = () => useSelector((state: RootState) => state.panel.connectionContent.isCreatingConnection); -export const useIsPanelCollapsed = () => useSelector(createSelector(getPanelState, (state) => state.isCollapsed)); +export const useIsPanelCollapsed = () => useSelector((state: RootState) => state.panel.isCollapsed); -export const useIsPanelLoading = () => useSelector(createSelector(getPanelState, (state) => state.isLoading)); +export const useIsPanelLoading = () => useSelector((state: RootState) => state.panel.isLoading); export const useIsNodePinnedToOperationPanel = (nodeId: string) => useSelector( - createSelector(getPanelState, (state) => { - return ( - (state.operationContent.alternateSelectedNode?.nodeId ?? '') === nodeId && - (state.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned' - ); - }) + (state: RootState) => + (state.panel.operationContent.alternateSelectedNode?.nodeId ?? '') === nodeId && + (state.panel.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned' ); export const useIsAlternateNodePinned = () => - useSelector(createSelector(getPanelState, (state) => (state.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned')); + useSelector((state: RootState) => (state.panel.operationContent.alternateSelectedNode?.persistence ?? '') === 'pinned'); export const useIsNodeSelectedInOperationPanel = (nodeId: string) => - useSelector(createSelector(getPanelState, (state) => (state.operationContent.selectedNodeId ?? '') === nodeId)); + useSelector((state: RootState) => (state.panel.operationContent.selectedNodeId ?? '') === nodeId); export const useIsPanelInPinnedViewMode = (): boolean => { const selectedNodeId = useOperationPanelSelectedNodeId(); @@ -67,24 +62,23 @@ export const useIsPanelInPinnedViewMode = (): boolean => { }; export const useOperationAlternateSelectedNodeId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.alternateSelectedNode?.nodeId ?? '')); + useSelector((state: RootState) => state.panel.operationContent.alternateSelectedNode?.nodeId ?? ''); export const useOperationAlternateSelectedNode = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.alternateSelectedNode ?? {})); + useSelector((state: RootState) => state.panel.operationContent.alternateSelectedNode ?? emptyAlternateSelectedNode); export const useOperationPanelAlternateNodeActiveTabId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.alternateSelectedNode?.activeTabId)); + useSelector((state: RootState) => state.panel.operationContent.alternateSelectedNode?.activeTabId); -export const useOperationPanelSelectedNodeId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent?.selectedNodeId ?? '')); +export const useOperationPanelSelectedNodeId = () => useSelector((state: RootState) => state.panel.operationContent?.selectedNodeId ?? ''); export const useOperationPanelSelectedNodeActiveTabId = () => - useSelector(createSelector(getPanelState, (state) => state.operationContent.selectedNodeActiveTabId)); + useSelector((state: RootState) => state.panel.operationContent.selectedNodeActiveTabId); -export const usePanelLocation = () => useSelector(createSelector(getPanelState, (state) => state.location)); +export const usePanelLocation = () => useSelector((state: RootState) => state.panel.location); -export const usePreviousPanelMode = () => useSelector(createSelector(getPanelState, (state) => state.previousPanelMode)); +export const usePreviousPanelMode = () => useSelector((state: RootState) => state.panel.previousPanelMode); -export const useIsAddingAgentTool = () => useSelector(createSelector(getPanelState, (state) => state.discoveryContent.isAddingAgentTool)); +export const useIsAddingAgentTool = () => useSelector((state: RootState) => state.panel.discoveryContent.isAddingAgentTool); -export const useIsAddingMcpServer = () => useSelector(createSelector(getPanelState, (state) => state.discoveryContent.isAddingMcpServer)); +export const useIsAddingMcpServer = () => useSelector((state: RootState) => state.panel.discoveryContent.isAddingMcpServer); From 08102395c69861f19ae781e6d7183eb6622eb2c4 Mon Sep 17 00:00:00 2001 From: Carlos Castro Date: Thu, 16 Jul 2026 14:14:55 -0400 Subject: [PATCH 2/2] chore: Restore localization catalog Undo unrelated localization extraction changes introduced by the commit hook. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 78b1618e-11be-48d0-984e-6709297a646e --- Localize/lang/strings.json | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Localize/lang/strings.json b/Localize/lang/strings.json index b8356c3a7a1..3a382abf132 100644 --- a/Localize/lang/strings.json +++ b/Localize/lang/strings.json @@ -39,6 +39,7 @@ "+l5XmZ": "Enter a positive integer between {min} and {max}", "+mAJR3": "(UTC+08:00) Kuala Lumpur, Singapore", "+mNJQl": "Create a logic app", + "+nCfrr": "Delete assertion", "+nKZHB": "Can't upload file. Please try again. Error: {errorMessage}", "+oX/2M": "Add actions to define the steps in your workflow", "+oelX4": "Required. The string to examine.", @@ -385,6 +386,7 @@ "5R1r3q": "Create a new connection", "5SAQOb": "Authority", "5Tqzsm": "Categorization", + "5U6Dee": "Action Result", "5Vbd0e": "Select a knowledge hub", "5VhY2X": "Loading models...", "5WTRY8": "Errors", @@ -425,6 +427,7 @@ "6776lH": "Processing...", "67FI5P": "Integration service environment", "68UJHa": "This list shows the new resources to create for your logic app and existing resources if any.", + "69+CIW": "View workflow", "6ASWgB": "Confirm that you want to delete this MCP server group? You can't undo this action.", "6D5fAm": "Trigger", "6DZp5H": "Search", @@ -497,6 +500,7 @@ "7MP7FJ": "How do values resolve at runtime?", "7MbFEx": "Select a connection", "7N+Zcl": "App Service Plan", + "7PTnxD": "New assertion", "7PtWvu": "(UTC-05:00) Eastern Time (US & Canada)", "7QymrD": "Required. The string from which the substring is taken.", "7ScdN6": "Name", @@ -530,6 +534,7 @@ "8+0teU": "Parameter name already exists.", "8+TVCG": "Edit: {selectedOperationSummary}", "8/Vjz3": "Knowledge base", + "805nCJ": "Error Message", "80T6Ut": "The deployment is taking longer than expected. Please check the Azure portal for more details.", "80z7j2": "Key-based authentication", "810OUB": "When triggered by another workflow", @@ -714,6 +719,7 @@ "BYrP8F": "Number", "BYsNzz": "Your template has been unpublished.", "Bewmet": "Array", + "BfGFkk": "Test icon", "Bft/H3": "All the benefits of Stateful, plus the option to build AI agents in your workflow to automate complex tasks.", "BjrVzW": "Resource group", "Bkc/+3": "Retry policy minimum interval is invalid, must match ISO 8601 duration format", @@ -908,6 +914,7 @@ "FTrMxN": "Close", "FUXlzD": "Run ID: {id}", "FUuFlC": "Default", + "FUvA4o": "Must provide a condition expression.", "FXLR5M": "{hours, plural, one {# hour} other {# hours}}", "FYtDP0": "Enter an email address.", "Fcvgvg": "Search", @@ -943,6 +950,7 @@ "GQnN3U": "Explain flow", "GR5+k2": "AI and machine learning capabilities", "GSzT3T": "Refresh", + "GX3fkR": "New assertion", "GXFvm+": "To enable chat client for production, setup authentication.", "GYu5XE": "Failed to load run details", "GYui13": "Continue editing", @@ -985,6 +993,7 @@ "H9pzpO": "Deleted the following hub artifacts:", "HDqP2g": "Required. The key name of the form data value to return.", "HET2nV": "Add a Variable", + "HF2SNx": "Is successful", "HF4xfS": "Enter agent name...", "HFt+tF": "Favorites", "HH970i": "Month", @@ -1162,6 +1171,7 @@ "LBlM+D": "Not specified", "LCRHQ9": "(UTC+12:00) Fiji", "LElaX3": "Next flow suggestion", + "LG7hSo": "Assertions", "LGUiVk": "Public access", "LLJrOT": "Description", "LMB8am": "Creating...", @@ -1180,6 +1190,7 @@ "LZm3ze": "Add a parallel branch", "LaFlFh": "Removed this action", "Ld62T8": "Delete", + "LdBN0m": "Assertion name", "LdITnG": "(UTC-03:00) Cayenne, Fortaleza", "LeR+TX": "Zoom in", "Lft/is": "Add new", @@ -1397,6 +1408,7 @@ "Peg6ZT": "Setting errors", "Pehyf2": "Enter agent instructions...", "PfCJlN": "Workflow functions", + "PhBS5+": "Enter name", "PjBcG3": "(UTC+02:00) Amman", "Pk4cb+": "Tracked properties", "Pnt0Xj": "Explain how to receive files from SFTP server.", @@ -1624,6 +1636,7 @@ "U3iWVd": "Generates an array of integers starting from a certain number", "U4zovj": "Runs {onTime} {onDays}", "U6V60S": "Validation failed :", + "U7UAV0": "Mocked Results Tab", "U82s8v": "Select a subscription, resource group and Logic App instance to find the workflows you want to convert to templates. Your changes apply only to this template and won't affect the original workflows.", "U9SHxw": "Code", "UCNM4L": "To reference a parameter, use the dynamic content list.", @@ -1715,6 +1728,7 @@ "VbMYd8": "Triggers tell your app when to start running. Each workflow needs at least one trigger.", "VchR9d": "Headers", "Vecdzb": "Logic app details", + "VfUtlo": "Save unit test definition", "Vi5TIV": "No warnings found.", "ViOMjt": "Use the chat client to talk to your agent.", "VjvWve": "Microsoft Authored", @@ -1989,6 +2003,7 @@ "_+l5XmZ.comment": "description of maximum waiting runs setting", "_+mAJR3.comment": "Time zone value ", "_+mNJQl.comment": "Create new logic app link", + "_+nCfrr.comment": "Create Assertion Text", "_+nKZHB.comment": "Error message when file upload fails in add files panel", "_+oX/2M.comment": "Description of what Actions are, on a tooltip about Actions", "_+oelX4.comment": "Required string parameter to check if is integer using isInt function", @@ -2335,6 +2350,7 @@ "_5R1r3q.comment": "Button text to create a new connection", "_5SAQOb.comment": "Authority Label Display Name", "_5Tqzsm.comment": "Categorization section title", + "_5U6Dee.comment": "The label for the action result dropdown in the unit test panel.", "_5Vbd0e.comment": "Placeholder text for knowledge hub dropdown", "_5VhY2X.comment": "Placeholder while models load for create form", "_5WTRY8.comment": "The tab label for the errors tab on the errors panel", @@ -2375,6 +2391,7 @@ "_6776lH.comment": "Processing message in the chatbot", "_67FI5P.comment": "ISE divider label", "_68UJHa.comment": "The aria label for the resources table", + "_69+CIW.comment": "View workflow button text", "_6ASWgB.comment": "Content for the delete MCP server group modal", "_6D5fAm.comment": "Tag for trigger operations", "_6DZp5H.comment": "Placeholder text for search connectors", @@ -2447,6 +2464,7 @@ "_7MP7FJ.comment": "Link text for learning more about tool parameters", "_7MbFEx.comment": "Title for step 1 - connection selection", "_7N+Zcl.comment": "The type for app service plan resource", + "_7PTnxD.comment": "Heading title for an assertion without name", "_7PtWvu.comment": "Time zone value ", "_7QymrD.comment": "Required string parameter required to obtain substring", "_7ScdN6.comment": "Deployment model resource name label", @@ -2480,6 +2498,7 @@ "_8+0teU.comment": "Error message when the workflow parameter name already exists.", "_8+TVCG.comment": "Title for edit operation panel", "_8/Vjz3.comment": "Title for knowledge hub editor", + "_805nCJ.comment": "Label for the error message input field", "_80T6Ut.comment": "Error message shown when deployment times out", "_80z7j2.comment": "Description for key-based authentication", "_810OUB.comment": "Workflow execution trigger category", @@ -2664,6 +2683,7 @@ "_BYrP8F.comment": "Placeholder title for a newly inserted Number parameter", "_BYsNzz.comment": "Title for the toaster after unpublishing template.", "_Bewmet.comment": "Title for array dropdown input setting", + "_BfGFkk.comment": "Test icon aria label", "_Bft/H3.comment": "Autonomous agents workflow description", "_BjrVzW.comment": "Label for choosing resource group", "_Bkc/+3.comment": "error message for invalid minimum retry interval", @@ -2858,6 +2878,7 @@ "_FTrMxN.comment": "Button text for closing the panel", "_FUXlzD.comment": "Label for the run ID", "_FUuFlC.comment": "Default, the backup option if none other apply", + "_FUvA4o.comment": "Error message when the assertion condition expression is empty.", "_FXLR5M.comment": "A duration of time shown in hours", "_FYtDP0.comment": "The placeholder description for a newly inserted 'Email' parameter.", "_Fcvgvg.comment": "Default placeholder for search box that searches dropdown options", @@ -2893,6 +2914,7 @@ "_GQnN3U.comment": "Chatbot prompt to explain the flow", "_GR5+k2.comment": "AI Agent category description", "_GSzT3T.comment": "Refresh button text to reload agent details", + "_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", @@ -2935,6 +2957,7 @@ "_H9pzpO.comment": "Content for the toaster after successfully deleting hub artifacts, with the names of the deleted artifacts", "_HDqP2g.comment": "Required string parameter to be used as key for triggerFormDataValue function", "_HET2nV.comment": "label to add a variable", + "_HF2SNx.comment": "Successful action result", "_HF4xfS.comment": "Placeholder for agent name input", "_HFt+tF.comment": "Favorites category", "_HH970i.comment": "Frequency value ", @@ -3112,6 +3135,7 @@ "_LBlM+D.comment": "The status message to show not specified in monitoring view.", "_LCRHQ9.comment": "Time zone value ", "_LElaX3.comment": "Text for button that shows the next flow suggestion", + "_LG7hSo.comment": "Unit test assertions button", "_LGUiVk.comment": "Label for the public access field", "_LLJrOT.comment": "Label for the operation description field", "_LMB8am.comment": "Button text to show a connection is being created", @@ -3130,6 +3154,7 @@ "_LZm3ze.comment": "Text for button to add a parallel branch", "_LaFlFh.comment": "Chatbot removed operation sentence format", "_Ld62T8.comment": "Button text for deleting selected workflows", + "_LdBN0m.comment": "Assertion field name title", "_LdITnG.comment": "Time zone value ", "_LeR+TX.comment": "Aria label for a button that zooms in on the workflow", "_Lft/is.comment": "Button to add a new connection", @@ -3347,6 +3372,7 @@ "_Peg6ZT.comment": "Header for the setting errors subsection", "_Pehyf2.comment": "Placeholder for instructions textarea", "_PfCJlN.comment": "Label for workflow functions", + "_PhBS5+.comment": "Assertion field name placeholder", "_PjBcG3.comment": "Time zone value ", "_Pk4cb+.comment": "title for tracked properties setting", "_Pnt0Xj.comment": "Chatbot suggestion message to recieve specific files from SFTP server", @@ -3574,6 +3600,7 @@ "_U3iWVd.comment": "Label for description of custom range Function", "_U4zovj.comment": "Recurrence schedule description on days of week at times", "_U6V60S.comment": "Error message title for workflow validation errors", + "_U7UAV0.comment": "An accessibility label that describes the mocked results tab", "_U82s8v.comment": "Label for the logic app resource selection description", "_U9SHxw.comment": "Code view title", "_UCNM4L.comment": "Description for Workflow Parameters Part 2", @@ -3665,6 +3692,7 @@ "_VbMYd8.comment": "Description of what Triggers are, on a tooltip about Triggers", "_VchR9d.comment": "Headers", "_Vecdzb.comment": "Logic app details step title", + "_VfUtlo.comment": "Save unit test button", "_Vi5TIV.comment": "Text to show when no warnings exist", "_ViOMjt.comment": "Option 2 description when auth is enabled", "_VjvWve.comment": "Label text for Microsoft authored templates tab", @@ -3922,6 +3950,7 @@ "_aSnCCB.comment": "Header for resource lcoation", "_aV/nLS.comment": "Frequency value ", "_aWcxdZ.comment": "Label text custom templates tab", + "_aWkG01.comment": "Unsupported message for mock results tab", "_aYTy7X.comment": "The status message to show in monitoring view.", "_aZtqSZ.comment": "Default error message for deployment model resource creation", "_afCjXx.comment": "Custom time range start label", @@ -4050,6 +4079,7 @@ "_cwHxwb.comment": "Text for create connection button", "_cx1uqu.comment": "Label for wrapping selected actions in a scope", "_cyEBus.comment": "Time zone value ", + "_cySYfO.comment": "Failed action result", "_d020eg.comment": "Label for description of custom first Function", "_d0BKdI.comment": "Message displayed when search returns no results", "_d0wgcV.comment": "Indicates that the run is currently waiting", @@ -4100,6 +4130,7 @@ "_dsz+Ae.comment": "Label for description of custom split Function", "_duhwio.comment": "Label for the API key", "_dwrqEc.comment": "Warnings section title", + "_dzPAxq.comment": "Edit Assertion Text", "_e+GuGo.comment": "Placeholder title for a newly inserted Text parameter", "_e00zot.comment": "Recurrence parameter group title", "_e1+Gqi.comment": "Description for resource location section.", @@ -4225,6 +4256,7 @@ "_gWNQQQ.comment": "Title for the resource selection section", "_gWyYg0.comment": "Time zone value ", "_gYaVvl.comment": "Error validation message for floats", + "_gZRdSs.comment": "Label for the error code input field", "_gaHI0k.comment": "Message to show when 0 or more than 2 items are present in the overflow menu", "_gc6v4X.comment": "Text for system assigned managed identity", "_gcn3Jg.comment": "Placeholder for the group description input field", @@ -4284,6 +4316,7 @@ "_hRVVdR.comment": "Chatbot undo button for undoing assistant change to flow", "_hTjAB+.comment": "Error message when tool name exceeds maximum characters", "_hUnLzB.comment": "Chatbot progress message shown while generating the final workflow from connector results", + "_hW7oe7.comment": "Assertion field description placeholder", "_hZqQdt.comment": "Time zone value ", "_haeWoU.comment": "Error message when splitOn cannot be evaluated", "_hbOvB4.comment": "Dislike button text for suggested flow", @@ -4436,6 +4469,7 @@ "_kM+Mr0.comment": "Loading text when items are being fetched", "_kN6kce.comment": "Label for description of custom decodeBase64 Function", "_kSK9Pq.comment": "Time zone value ", + "_kSXjTx.comment": "Assertion field no description text", "_kU4VfD.comment": "Choice group first choice: Stateful Type", "_kVwJXt.comment": "Time zone value ", "_kXn5e0.comment": "Chabot input placeholder text", @@ -4464,6 +4498,7 @@ "_kxv92S.comment": "Confirmation message for deleting a server", "_l/3yJr.comment": "Text to show when there is an error with the connection", "_l/9YHQ.comment": "Time zone value ", + "_l0hO5f.comment": "Error message when the workflow assertion name already exists.", "_l2YXln.comment": "Chatbot prompt to replace an action", "_l36V56.comment": "This is a time duration in full non abbreviated format", "_l3nn+9.comment": "Required field label", @@ -4476,6 +4511,7 @@ "_l9TY/4.comment": "ARIA label and tooltip text for the popup button", "_lA/sHA.comment": "Accessibility label for a button to copy all text in a value box", "_lB56l2.comment": "Error validation message for Numbers", + "_lC+EbT.comment": "The tab label for the mocked results tab on the operation panel", "_lFWXhc.comment": "The tab label for the monitoring parameters tab on the operation panel", "_lIVS+K.comment": "Name of the organization or developer that published this template", "_lK+Vzo.comment": "This is an option in a dropdown where users can select type Secure String for their parameter.", @@ -4643,6 +4679,7 @@ "_oBK3A4.comment": "Accessible label for editable expression token", "_oChTO9.comment": "Accessibility label for the select workflow row checkbox", "_oDHXKh.comment": "Display name for item output", + "_oFq3ng.comment": "Assertions Panel Title", "_oGINHJ.comment": "Workflow version filter label", "_oIRKrF.comment": "Text to show no connections present in the template.", "_oJebOR.comment": "Button text displayed while saving authentication settings", @@ -4826,6 +4863,7 @@ "_s0Qaot.comment": "Workflow Parameters Panel", "_s2f0XK.comment": "Content for the toaster after creating a group in add files panel", "_s2ydQX.comment": "Error validation message for invalid JSON object. Do not remove the double single quotes around the display name, as it is needed to wrap the placeholder text.", + "_s4omwa.comment": "Assertion field description title", "_s5AOpV.comment": "Label for the title for panel header card", "_s5RV9B.comment": "Label for description of custom dayOfMonth Function", "_s5jHO7.comment": "Label for agent name input", @@ -4910,6 +4948,7 @@ "_tEqgJQ.comment": "Delete button disabled tooltip text", "_tGSsgZ.comment": "Placehodler text for dropdown", "_tH2pT1.comment": "Text for the Details page navigation heading", + "_tHDcfJ.comment": "Error message when the assertion name is empty.", "_tHFp5l.comment": "Title for the main section with the count of tools", "_tIl0ss.comment": "Title for YouTube video iframe", "_tImHz/.comment": "This is a time duration in abbreviated format", @@ -4994,6 +5033,7 @@ "_uh/+ZN.comment": "Time zone value ", "_ui3KuP.comment": "Header for the input errors category", "_ui7GCl.comment": "Hour of the day", + "_ujp53j.comment": "Assertion field condition title", "_ukB9Bs.comment": "Setup section description in info dialog", "_ukGRNP.comment": "Message shown when parallel branch addition is disabled on root in A2A workflows", "_umLmPm.comment": "Operation Panel", @@ -5290,6 +5330,7 @@ "aSnCCB": "Location", "aV/nLS": "Second", "aWcxdZ": "My Templates", + "aWkG01": "This operation does not support mocking. Mocking is only supported for operations that are connected to a service provider, function, API connection, or API Management.", "aYTy7X": "Cancelled", "aZtqSZ": "An error occurred while creating the deployment model resource.", "afCjXx": "Start", @@ -5418,6 +5459,7 @@ "cwHxwb": "Add connection", "cx1uqu": "Scope", "cyEBus": "(UTC-04:00) Cuiaba", + "cySYfO": "Has failed", "d020eg": "Returns the first element from the passed-in array or string.", "d0BKdI": "Search can't find any results.", "d0wgcV": "Waiting", @@ -5468,6 +5510,7 @@ "dsz+Ae": "Splits the string using a separator", "duhwio": "API key", "dwrqEc": "Warnings", + "dzPAxq": "Edit assertion", "e+GuGo": "Input", "e00zot": "How often do you want to check for items?", "e1+Gqi": "Select the resource location for your workflow", @@ -5593,6 +5636,7 @@ "gWNQQQ": "Project details", "gWyYg0": "(UTC+05:00) Ashgabat, Tashkent", "gYaVvl": "Enter a valid float.", + "gZRdSs": "Error Code", "gaHI0k": "{overflowItemsLength} more items", "gc6v4X": "System-assigned managed identity", "gcn3Jg": "Enter a description", @@ -5652,6 +5696,7 @@ "hRVVdR": "Undo", "hTjAB+": "Tool name cannot exceed maximum characters", "hUnLzB": "Building workflow...", + "hW7oe7": "Enter description", "hZqQdt": "(UTC+02:00) Gaza, Hebron", "haeWoU": "Failed to evaluate outputs because splitOn {splitOn} cannot be evaluated. As a result, this operation's outputs might not be correctly visible in subsequent actions", "hbOvB4": "This isn't what I'm looking for", @@ -5804,6 +5849,7 @@ "kM+Mr0": "Loading...", "kN6kce": "Returns a string representation of an input based64 string", "kSK9Pq": "(UTC-05:00) Bogota, Lima, Quito, Rio Branco", + "kSXjTx": "No description", "kU4VfD": "Stateful", "kVwJXt": "(UTC+04:00) Abu Dhabi, Muscat", "kXn5e0": "Ask a question about this workflow or about Azure Logic Apps as a whole ...", @@ -5832,6 +5878,7 @@ "kxv92S": "Successfully deleted MCP server: ''{serverName}''.", "l/3yJr": "Invalid connection", "l/9YHQ": "(UTC+01:00) Windhoek", + "l0hO5f": "Assertion name already exists.", "l2YXln": "Replace action with", "l36V56": "{hours} hours {minutes} minutes", "l3nn+9": "Required field", @@ -5844,6 +5891,7 @@ "l9TY/4": "Open in popup", "lA/sHA": "Copy the value of ''{label}'' to the clipboard", "lB56l2": "Enter a valid number.", + "lC+EbT": "Mocked Results", "lFWXhc": "Workflow", "lIVS+K": "By", "lK+Vzo": "Secure string", @@ -6011,6 +6059,7 @@ "oBK3A4": "Edit {tokenTitle} expression", "oChTO9": "Select workflow row checkbox label", "oDHXKh": "Item", + "oFq3ng": "Assertions", "oGINHJ": "Version", "oIRKrF": "No connections in this template", "oJebOR": "Saving...", @@ -6194,6 +6243,7 @@ "s0Qaot": "Workflow Parameters Panel", "s2f0XK": "Group {name} has been created and selected.", "s2ydQX": "''Value'' must be a valid JSON object", + "s4omwa": "Description", "s5AOpV": "Card title", "s5RV9B": "Returns the day of year component of a string timestamp", "s5jHO7": "Agent name", @@ -6278,6 +6328,7 @@ "tEqgJQ": "Cannot delete the last parameter", "tGSsgZ": "select an identity", "tH2pT1": "Return to search", + "tHDcfJ": "Must provide the assertion name.", "tHFp5l": "Tools", "tIl0ss": "YouTube video player", "tImHz/": "{days}d {hours}h", @@ -6362,6 +6413,7 @@ "uh/+ZN": "(UTC-03:00) Greenland", "ui3KuP": "Operation errors", "ui7GCl": "16", + "ujp53j": "Condition expression", "ukB9Bs": "To enable authentication for production use, follow the authentication setup guide.", "ukGRNP": "Cannot add parallel branches on the root level in agent to agent workflows", "umLmPm": "Operation Panel",