diff --git a/src/platform/plugins/shared/console/public/application/containers/editor/monaco_editor_actions_provider.test.ts b/src/platform/plugins/shared/console/public/application/containers/editor/monaco_editor_actions_provider.test.ts index fa290a69ab2c8..670173b8faffd 100644 --- a/src/platform/plugins/shared/console/public/application/containers/editor/monaco_editor_actions_provider.test.ts +++ b/src/platform/plugins/shared/console/public/application/containers/editor/monaco_editor_actions_provider.test.ts @@ -281,6 +281,24 @@ describe('Editor actions provider', () => { ); }); + it('orders method suggestions with GET first and DELETE last using sortText', async () => { + // Monaco sorts completion items by sortText, falling back to label. Without + // an explicit sortText, alphabetical sorting puts DELETE first (#259251). + mockGetParsedRequests.mockResolvedValue([]); + const completionItems = await editorActionsProvider.provideCompletionItems( + mockModel, + mockPosition, + mockContext + ); + const sortedByMonaco = [...(completionItems?.suggestions ?? [])].sort((a, b) => + String(a.sortText ?? a.label).localeCompare(String(b.sortText ?? b.label)) + ); + const orderedLabels = sortedByMonaco.map((s) => s.label); + expect(orderedLabels[0]).toBe('GET'); + expect(orderedLabels[orderedLabels.length - 1]).toBe('DELETE'); + expect(orderedLabels).toEqual(['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE']); + }); + it('returns completion items for url path if method already typed in', async () => { // mock a parsed request that only has a method mockGetParsedRequests.mockResolvedValue([ diff --git a/src/platform/plugins/shared/console/public/application/containers/editor/utils/autocomplete_utils.ts b/src/platform/plugins/shared/console/public/application/containers/editor/utils/autocomplete_utils.ts index 4d75a0e6b064e..6d5602d512884 100644 --- a/src/platform/plugins/shared/console/public/application/containers/editor/utils/autocomplete_utils.ts +++ b/src/platform/plugins/shared/console/public/application/containers/editor/utils/autocomplete_utils.ts @@ -59,21 +59,27 @@ const filterTermsWithoutName = (terms: ResultTerm[]): ResultTerm[] => terms.filter((term) => term.name !== undefined && term.name !== ''); /* - * This function returns an array of completion items for the request method + * This function returns an array of completion items for the request method. + * + * The order is deliberate: Monaco sorts completion items by `sortText` and + * falls back to alphabetical label sorting, which would otherwise put DELETE + * first (#259251). GET is the safest verb to accept by default and DELETE + * the most destructive, so we pin GET first and DELETE last. */ -const autocompleteMethods = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH']; +const autocompleteMethods = ['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE']; export const getMethodCompletionItems = ( model: monaco.editor.ITextModel, position: monaco.Position ): monaco.languages.CompletionItem[] => { // get the word before suggestions to replace when selecting a suggestion from the list const wordUntilPosition = model.getWordUntilPosition(position); - return autocompleteMethods.map((method) => ({ + return autocompleteMethods.map((method, index) => ({ label: method, insertText: method, detail: i18nTexts.method, // only used to configure the icon kind: monaco.languages.CompletionItemKind.Constant, + sortText: String(index), range: { // replace the whole word with the suggestion startColumn: wordUntilPosition.startColumn,