diff --git a/CHANGELOG.md b/CHANGELOG.md index 799a590..e1b7e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This changelog is following the recommended format by [keepachangelog](https://k - Capability to export a single source, rule, transform or identity profile from the tree view - Capability to import a sp-config +- Can refresh identities under an identity profile (cf. [#30](https://github.com/yannick-beot-sp/vscode-sailpoint-identitynow/issues/30)) ### Fixed diff --git a/README.md b/README.md index ae32997..8b0c909 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The SailPoint IdentityNow extension makes it easy to: - View, edit, enable, disable, and test workflows and view execution history - View, create, edit, delete connector rules and export/import the script of a rule - View, edit, delete service desk integrations -- View, edit, delete identity profiles and lifecycle states +- View, edit, delete identity profiles and lifecycle states, and refreshes all the identities under a profile ## Installation diff --git a/package.json b/package.json index f2055e2..b6604b0 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "onCommand:vscode-sailpoint-identitynow.connector-rule.export-script.view", "onCommand:vscode-sailpoint-identitynow.identity-profiles.sort.name", "onCommand:vscode-sailpoint-identitynow.identity-profiles.sort.priority", + "onCommand:vscode-sailpoint-identitynow.identity-profile.refresh", "onFileSystem:idn", "onUri" ], @@ -239,6 +240,10 @@ "command": "vscode-sailpoint-identitynow.connector-rule.export-script.view", "title": "Export the script..." }, + { + "command": "vscode-sailpoint-identitynow.identity-profile.refresh", + "title": "Refresh all identities" + }, { "command": "vscode-sailpoint-identitynow.identity-profiles.sort.name", "title": "Sort by name", @@ -334,6 +339,10 @@ "command": "vscode-sailpoint-identitynow.transform.evaluate", "when": "never" }, + { + "command": "vscode-sailpoint-identitynow.identity-profile.refresh", + "when": "never" + }, { "command": "vscode-sailpoint-identitynow.identity-profiles.sort.name", "when": "never" @@ -506,6 +515,10 @@ "command": "vscode-sailpoint-identitynow.connector-rule.export-script.view", "when": "view == vscode-sailpoint-identitynow.view && viewItem == connector-rule" }, + { + "command": "vscode-sailpoint-identitynow.identity-profile.refresh", + "when": "view == vscode-sailpoint-identitynow.view && viewItem == identity-profile" + }, { "command": "vscode-sailpoint-identitynow.refresh", "when": "view == vscode-sailpoint-identitynow.view && viewItem == identity-profiles", @@ -521,11 +534,6 @@ "when": "view == vscode-sailpoint-identitynow.view && viewItem == identity-profiles", "group": "inline@1" }, - { - "command": "vscode-sailpoint-identitynow.refresh", - "when": "view == vscode-sailpoint-identitynow.view && viewItem == identity-profile", - "group": "inline@1" - }, { "command": "vscode-sailpoint-identitynow.open-resource", "when": "view == vscode-sailpoint-identitynow.view && viewItem == identity-profile" diff --git a/src/commands/constants.ts b/src/commands/constants.ts index 6638a85..2d52f21 100644 --- a/src/commands/constants.ts +++ b/src/commands/constants.ts @@ -31,6 +31,7 @@ export const EXPORT_CONNECTOR_RULE_SCRIPT_EDITOR = 'vscode-sailpoint-identitynow export const EXPORT_CONNECTOR_RULE_SCRIPT_VIEW = 'vscode-sailpoint-identitynow.connector-rule.export-script.view'; export const SORT_IDENTITY_PROFILES_BY_NAME = 'vscode-sailpoint-identitynow.identity-profiles.sort.name'; export const SORT_IDENTITY_PROFILES_BY_PRIORITY = 'vscode-sailpoint-identitynow.identity-profiles.sort.priority'; +export const REFRESH_IDENTITY_PROFILE = 'vscode-sailpoint-identitynow.identity-profile.refresh'; export const TREE_VIEW = 'vscode-sailpoint-identitynow.view'; export const WORKFLOW_TESTER_VIEW = 'vscode-sailpoint-identitynow.workflow.test-view'; \ No newline at end of file diff --git a/src/commands/importConfig.ts b/src/commands/importConfig.ts index 0b8a26e..3c8d635 100644 --- a/src/commands/importConfig.ts +++ b/src/commands/importConfig.ts @@ -65,7 +65,7 @@ class BaseImporter { throw new Error("Could not import config: " + jobStatus.message); } - const importJobresult = await client.getImportJobResult(jobId); + const importJobresult:any = await client.getImportJobResult(jobId); for (const key in importJobresult.results) { if (message.length > 0) { message += ", "; diff --git a/src/commands/refreshIdentityProfile.ts b/src/commands/refreshIdentityProfile.ts new file mode 100644 index 0000000..0bab2c8 --- /dev/null +++ b/src/commands/refreshIdentityProfile.ts @@ -0,0 +1,28 @@ +import * as vscode from 'vscode'; +import * as commands from './constants'; +import { IdentityNowResourceTreeItem, IdentityProfileTreeItem, TransformsTreeItem } from '../models/IdentityNowTreeItem'; +import { IdentityNowClient } from '../services/IdentityNowClient'; +import { getPathByUri } from '../utils/UriUtils'; + + +export async function refreshIdentityProfile(node?: IdentityProfileTreeItem): Promise { + + console.log("> refreshIdentityProfile", node); + // assessing that item is a IdentityProfileTreeItem + if (node === undefined || !(node instanceof IdentityProfileTreeItem)) { + console.log("WARNING: refreshIdentityProfile: invalid item", node); + throw new Error("refreshIdentityProfile: invalid item"); + } + + + const client = new IdentityNowClient(node.tenantId, node.tenantName); + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Refreshing ${node.label}...`, + cancellable: false + }, async (task, token) => { + await client.refreshIdentityProfile(node.id); + }).then(async () => + await vscode.window.showInformationMessage(`Successfully refreshed ${node.label}`)); + +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 916793f..eb29a80 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -27,6 +27,7 @@ import { RenameTenantCommand } from './commands/renameTenant'; import { IdentityNowUriHandler } from './uriHandler'; import { SortIdentityProfileCommand } from './commands/sortIdentityProfile'; import { MenuImporter, PaletteImporter, TreeViewImporter } from './commands/importConfig'; +import { refreshIdentityProfile } from './commands/refreshIdentityProfile'; // this method is called when your extension is activated // your extension is activated the very first time the command is executed @@ -188,6 +189,10 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand(commands.EXPORT_CONNECTOR_RULE_SCRIPT_VIEW, exportScriptFromRuleCommand.exportScriptView, exportScriptFromRuleCommand)); + context.subscriptions.push( + vscode.commands.registerCommand(commands.REFRESH_IDENTITY_PROFILE, + refreshIdentityProfile)); + const sortIdentityProfileCommand = new SortIdentityProfileCommand(); context.subscriptions.push( vscode.commands.registerCommand(commands.SORT_IDENTITY_PROFILES_BY_NAME, diff --git a/src/services/IdentityNowClient.ts b/src/services/IdentityNowClient.ts index fd769da..38df8ce 100644 --- a/src/services/IdentityNowClient.ts +++ b/src/services/IdentityNowClient.ts @@ -15,6 +15,7 @@ import { Readable } from "stream"; import { ImportJobResults, JobStatus } from "../models/JobStatus"; export class IdentityNowClient { + constructor( private readonly tenantId: string, private readonly tenantName: string @@ -496,8 +497,7 @@ export class IdentityNowClient { objectOptions = {} ): Promise { console.log("> startExportJob", objectTypes); - let endpoint = EndpointUtils.getBetaUrl(this.tenantName); - endpoint += "/sp-config/export"; + const endpoint = EndpointUtils.getBetaUrl(this.tenantName) + "/sp-config/export"; console.log("endpoint = " + endpoint); const headers = await this.prepareHeaders(); @@ -796,6 +796,21 @@ export class IdentityNowClient { // identityProfiles.sort(compareByName); return serviceDesks; } + + public async refreshIdentityProfile(identityProfileId: string):Promise { + console.log("> refreshIdentityProfile", identityProfileId); + const endpoint = EndpointUtils.getBetaUrl(this.tenantName) + `/identity-profiles/${identityProfileId}/refresh-identities`; + console.log("endpoint = " + endpoint); + const headers = await this.prepareHeaders(); + const resp = await fetch(endpoint, { + method: "POST", + headers: headers + }); + + if (!resp.ok) { + throw new Error(resp.statusText); + } + } } export enum AggregationJob {