From b16d4a1fbc8d7a548da6b5b6cbc0f3993fa90438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannick=20B=C3=A9ot?= Date: Sat, 2 Mar 2024 22:55:30 +0100 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=20New=20Search=20Attribute=20Config?= =?UTF-8?q?=20#64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 26 ++++-- .../NewAttributeSearchConfigCommand.ts | 92 +++++++++++++++++++ src/commands/constants.ts | 2 + src/extension.ts | 7 ++ src/services/IdentityNowClient.ts | 8 ++ src/wizard/quickPickAccountSchemaStep.ts | 32 +++++++ 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 src/commands/NewAttributeSearchConfigCommand.ts create mode 100644 src/wizard/quickPickAccountSchemaStep.ts diff --git a/package.json b/package.json index d641f1a..4d9bc79 100644 --- a/package.json +++ b/package.json @@ -471,6 +471,11 @@ "light": "resources/light/import.svg", "dark": "resources/dark/import.svg" } + }, + { + "command": "vscode-sailpoint-identitynow.new-attribute-search.view", + "title": "New Search Attribute", + "icon": "$(add)" } ], "menus": { @@ -734,6 +739,10 @@ { "command": "vscode-sailpoint-identitynow.forms.import.icon-view", "when": "never" + }, + { + "command": "vscode-sailpoint-identitynow.new-attribute-search.view", + "when": "never" } ], "view/title": [ @@ -982,11 +991,11 @@ }, { "command": "vscode-sailpoint-identitynow.new-role.view", - "when": "view == vscode-sailpoint-identitynow.view && viewItem =~ /^roles/" + "when": "view == vscode-sailpoint-identitynow.view && viewItem == roles" }, { "command": "vscode-sailpoint-identitynow.new-role.view-icon", - "when": "view == vscode-sailpoint-identitynow.view && viewItem =~ /^roles/", + "when": "view == vscode-sailpoint-identitynow.view && viewItem == roles", "group": "inline@1" }, { @@ -1009,20 +1018,20 @@ }, { "command": "vscode-sailpoint-identitynow.csv.export.roles.view", - "when": "view == vscode-sailpoint-identitynow.view && viewItem =~ /^roles/" + "when": "view == vscode-sailpoint-identitynow.view && viewItem == roles" }, { "command": "vscode-sailpoint-identitynow.csv.export.roles-icon.view", - "when": "view == vscode-sailpoint-identitynow.view && viewItem =~ /^roles/", + "when": "view == vscode-sailpoint-identitynow.view && viewItem == roles", "group": "inline@3" }, { "command": "vscode-sailpoint-identitynow.csv.import.roles.view", - "when": "view == vscode-sailpoint-identitynow.view && viewItem =~ /^roles/" + "when": "view == vscode-sailpoint-identitynow.view && viewItem == roles" }, { "command": "vscode-sailpoint-identitynow.csv.import.roles-icon.view", - "when": "view == vscode-sailpoint-identitynow.view && viewItem =~ /^roles/", + "when": "view == vscode-sailpoint-identitynow.view && viewItem == roles", "group": "inline@2" }, { @@ -1046,6 +1055,11 @@ "command": "vscode-sailpoint-identitynow.forms.import.icon-view", "when": "view == vscode-sailpoint-identitynow.view && viewItem == form-definitions", "group": "inline@2" + }, + { + "command": "vscode-sailpoint-identitynow.new-attribute-search.view", + "when": "view == vscode-sailpoint-identitynow.view && viewItem == search-attributes", + "group": "inline@2" } ], "editor/context": [ diff --git a/src/commands/NewAttributeSearchConfigCommand.ts b/src/commands/NewAttributeSearchConfigCommand.ts new file mode 100644 index 0000000..f5ff19c --- /dev/null +++ b/src/commands/NewAttributeSearchConfigCommand.ts @@ -0,0 +1,92 @@ +import * as vscode from 'vscode'; +import { TenantService } from "../services/TenantService"; +import { SearchAttributesTreeItem } from '../models/IdentityNowTreeItem'; +import { IdentityNowClient } from '../services/IdentityNowClient'; +import { getResourceUri } from '../utils/UriUtils'; +import { SearchAttributeConfigBeta } from 'sailpoint-api-client'; +import { runWizard } from '../wizard/wizard'; +import { InputPromptStep } from '../wizard/inputPromptStep'; +import { Validator } from '../validator/validator'; +import { WizardContext } from '../wizard/wizardContext'; +import { QuickPickTenantStep } from '../wizard/quickPickTenantStep'; +import { openPreview } from '../utils/vsCodeHelpers'; +import { QuickPickSourceStep } from '../wizard/quickPickSourceStep'; +import { QuickPickAccountSchemaStep } from '../wizard/quickPickAccountSchemaStep'; +import * as commands from "../commands/constants"; + +const searchAttributeNameValidator = new Validator({ + required: true, + maxLength: 128, + regexp: '^[A-Za-z0-9 _:;,={}@()#-|^%$!?.*]+$' +}); + +/** + * Command used to create a Search Attribute + */ +export class NewAttributeSearchConfigCommand { + + constructor(private readonly tenantService: TenantService) { } + + async execute(node?: SearchAttributesTreeItem): Promise { + + console.log("> NewAttributeSearchConfigCommand.newRole", node); + const context: WizardContext = {}; + + // if the command is called from the Tree View + if (node !== undefined && node instanceof SearchAttributesTreeItem) { + context["tenant"] = await this.tenantService.getTenant(node.tenantId); + } + + let client: IdentityNowClient | undefined = undefined; + const values = await runWizard({ + title: "Creation of a search attribute", + hideStepCount: false, + promptSteps: [ + new QuickPickTenantStep( + this.tenantService, + async (wizardContext) => { + client = new IdentityNowClient( + wizardContext["tenant"].id, wizardContext["tenant"].tenantName); + }), + new InputPromptStep({ + name: "searchAttribute", + displayName: "search attribute", + options: { + validateInput: (s: string) => { return searchAttributeNameValidator.validate(s); } + } + }), + new QuickPickSourceStep(() => { return client!; }), + new QuickPickAccountSchemaStep(() => { return client!; }), + ] + }, context); + console.log({ values }); + if (values === undefined) { return; } + + const name = values["searchAttribute"].trim() + const searchAttribute: SearchAttributeConfigBeta = { + name: name, + displayName: name, + applicationAttributes: {} + } + searchAttribute.applicationAttributes[values["source"].id] = values["attribute"].name + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: 'Creating Attribute Search...', + cancellable: false + }, async (task, token) => { + + await client.createSearchAttribute(searchAttribute) + const newUri = getResourceUri( + values["tenant"].tenantName, + "accounts/search-attribute-config", + name, + name, + true + ) + + openPreview(newUri); + vscode.commands.executeCommand(commands.REFRESH_FORCED); + }); + } +} diff --git a/src/commands/constants.ts b/src/commands/constants.ts index 6093633..2379256 100644 --- a/src/commands/constants.ts +++ b/src/commands/constants.ts @@ -94,3 +94,5 @@ export const IMPORT_FORMS_ICON_VIEW = 'vscode-sailpoint-identitynow.forms.import export const EDIT_PUBLIC_IDENTITIES_CONFIG = 'vscode-sailpoint-identitynow.tenant.edit.public-identities-config'; export const EDIT_ACCESS_REQUEST_CONFIG = 'vscode-sailpoint-identitynow.tenant.edit.access-request-config'; + +export const NEW_SEARCH_ATTRIBUTE = 'vscode-sailpoint-identitynow.new-attribute-search.view' \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index d391348..24a0dfa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -56,6 +56,7 @@ import { WorkflowExportCommand } from './commands/workflow/WorkflowExportCommand import { WorkflowImporterTreeViewCommand } from './commands/workflow/WorkflowImporterTreeViewCommand'; import { EditPublicIdentitiesConfigCommand } from './commands/editPublicIdentitiesConfigCommand'; import { EditAccessRequestConfigCommand } from './commands/editAccessRequestConfigCommand'; +import { NewAttributeSearchConfigCommand } from './commands/NewAttributeSearchConfigCommand'; // this method is called when your extension is activated // your extension is activated the very first time the command is executed @@ -423,6 +424,12 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand(commands.IMPORT_FORMS_ICON_VIEW, formDefinitionImporterTreeViewCommand.execute, formDefinitionImporterTreeViewCommand)); + + // Attribute Search Config + const newAttributeSearchConfigCommand = new NewAttributeSearchConfigCommand(tenantService) + context.subscriptions.push( + vscode.commands.registerCommand(commands.NEW_SEARCH_ATTRIBUTE, + newAttributeSearchConfigCommand.execute, newAttributeSearchConfigCommand)); } // this method is called when your extension is deactivated diff --git a/src/services/IdentityNowClient.ts b/src/services/IdentityNowClient.ts index 621457a..ce84a5a 100644 --- a/src/services/IdentityNowClient.ts +++ b/src/services/IdentityNowClient.ts @@ -1463,6 +1463,14 @@ export class IdentityNowClient { const result = await api.getSearchAttributeConfig() return result.data; } + + public async createSearchAttribute(searchAttributeConfigBeta: SearchAttributeConfigBeta): Promise { + console.log("> createSearchAttribute"); + const apiConfig = await this.getApiConfiguration(); + const api = new SearchAttributeConfigurationBetaApi(apiConfig); + await api.createSearchAttributeConfig({searchAttributeConfigBeta}) + } + ///////////////////////// //#endregion Search attributes ///////////////////////// diff --git a/src/wizard/quickPickAccountSchemaStep.ts b/src/wizard/quickPickAccountSchemaStep.ts new file mode 100644 index 0000000..e6dc02e --- /dev/null +++ b/src/wizard/quickPickAccountSchemaStep.ts @@ -0,0 +1,32 @@ + +import * as vscode from 'vscode'; +import { QuickPickPromptStep } from "./quickPickPromptStep"; +import { WizardContext } from "./wizardContext"; +import { IdentityNowClient } from "../services/IdentityNowClient"; + +export class QuickPickAccountSchemaStep extends QuickPickPromptStep { + constructor( + getIdentityNowClient: () => IdentityNowClient, + ) { + super({ + name: "attribute", + options: { + matchOnDescription: true, + matchOnDetail: true + }, + skipIfOne: true, + items: async (context: WizardContext): Promise => { + const client = getIdentityNowClient(); + const source = context["source"]; + const results = (await client.getSchemas(source.id)) + .find(x => x.name === "account")?.attributes?.map(x => ({ + ...x, + label: x.name, + description: x.description + })); + return results; + } + }) + } +} +