From b6d5268045fa0f95aea6032857a215c3c02f3339 Mon Sep 17 00:00:00 2001 From: Philip Ellis Date: Thu, 21 Aug 2025 16:53:31 -0400 Subject: [PATCH] added docs for the rest of the app dev process --- .../ui-development-kit/accounts-list.mdx | 1179 +++++++++++------ docs/tools/ui-development-kit/deploying.md | 45 + .../ui-development-kit/error-handling.md | 138 +- .../img/theme-component.png | Bin 0 -> 68343 bytes docs/tools/ui-development-kit/index.md | 2 +- docs/tools/ui-development-kit/theming.md | 10 +- 6 files changed, 864 insertions(+), 510 deletions(-) create mode 100644 docs/tools/ui-development-kit/deploying.md create mode 100644 docs/tools/ui-development-kit/img/theme-component.png diff --git a/docs/tools/ui-development-kit/accounts-list.mdx b/docs/tools/ui-development-kit/accounts-list.mdx index 02ef03278..11678d30b 100644 --- a/docs/tools/ui-development-kit/accounts-list.mdx +++ b/docs/tools/ui-development-kit/accounts-list.mdx @@ -90,7 +90,10 @@ import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatTableModule } from '@angular/material/table'; import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatPaginatorModule } from '@angular/material/paginator'; import { SailPointSDKService } from '../sailpoint-sdk.service'; +import { AccountV2025 } from 'sailpoint-api-client'; @Component({ selector: 'app-accounts', @@ -102,12 +105,17 @@ import { SailPointSDKService } from '../sailpoint-sdk.service'; MatIconModule, MatTableModule, MatToolbarModule, + MatProgressSpinnerModule, + MatPaginatorModule ], templateUrl: './accounts.component.html', styleUrl: './accounts.component.scss', }) export class AccountsComponent implements OnInit { title = 'Accounts'; + loading = true; + accounts: AccountV2025[] = []; + displayedColumns: string[] = ['id', 'name', 'nativeIdentity', 'sourceId', 'disabled', 'locked', 'actions']; constructor(private sdk: SailPointSDKService) {} @@ -117,121 +125,122 @@ export class AccountsComponent implements OnInit { } private async loadAccounts() { + this.loading = true; try { - const accounts = await this.sdk.listAccounts(); - console.log('Loaded accounts:', accounts); + const response = await this.sdk.listAccounts(); + this.accounts = response.data as AccountV2025[]; + console.log('Loaded accounts:', this.accounts); } catch (error) { console.error('Error loading accounts:', error); + } finally { + this.loading = false; } } + + viewAccount(account: AccountV2025): void { + console.log('Viewing account:', account); + } } + ``` Return to your accounts list page. In the electron app, click View -> Toggle Developer Tools, you will see the response containing the accounts in the console after the page loads. Now that you have your account data, you need to display the data. You can add a table to the UI and display your results. -To do so, add this code to `src/routes/accounts/account-list/+page.svelte`: +To do so, add this code to `\accounts\accounts.component.html`:
Show code ```html - + + + Name + {{ account.name || '-' }} + -
-
-

List of all accounts

-
- {#await data.accountData} -
- -
- {:then accountData} - - {#if accountData.length === 0} -
-

No Accounts found

-
- {:else} -
- - - - - - - - - - - - - - {#each accountData as account} - - - - - - - - - - - - {/each} - -
Name Native Identity Source Created Modified Authoritative Features Has Entitlements
- {account.name} - - {account.nativeIdentity} - - {account.sourceName} - - {formatDate(account.created)} - - {formatDate(account.modified)} - - {account.authoritative} - - {account.features} - - {account.hasEntitlements} - -
- - -
-
-
- {/if} - {/await} + + + Native Identity + {{ account.nativeIdentity || '-' }} + + + + + Source + {{ account.sourceId || '-' }} + + + + + Disabled + + check_circle + cancel + + + + + + Locked + + lock + lock_open + + + + + + Actions + + + + + + + + + + +
+ No accounts found. +
+
+ + + ```
-Save the `+page.svelte` file and return to the accounts list page. You will see up to 250 accounts in the table. +Save the `\accounts\accounts.component.html` file and return to the accounts list page. You will see up to 250 accounts in the table. ## Pagination @@ -246,378 +255,744 @@ import TabItem from '@theme/TabItem'; import Tabs from '@theme/Tabs'; - + ```html - + + + Native Identity + {{ account.nativeIdentity || '-' }} + -
-
-

List of all accounts

-
- {#await data.accountData} -
- -
- {:then accountData} - - {#await data.totalCount then totalCount} - {#if totalCount > 250 || Number(data.params.limit) < totalCount} -
- -
- {/if} - {/await} - - {#if accountData.length === 0} -
-

No Accounts found

-
- {:else} -
- - - - - - - - - - - - - - {#each accountData as account} - - - - - - - - - - - - {/each} - -
Name Native Identity Source Created Modified Authoritative Features Has Entitlements
- {account.name} - - {account.nativeIdentity} - - {account.sourceName} - - {formatDate(account.created)} - - {formatDate(account.modified)} - - {account.authoritative} - - {account.features} - - {account.hasEntitlements} - -
- -
-
-
- {/if} - {/await} + + + Source + {{ account.sourceId || '-' }} + + + + + Disabled + + check_circle + cancel + + + + + + Locked + + lock + lock_open + + + + + + Actions + + + + + + + + + + + + + + +
+ No accounts found. +
+
+ + ```
- + ```typescript -import { createConfiguration } from '$lib/sailpoint/sdk.js'; -// highlight-next-line -import { getLimit, getPage } from '$lib/Utils.js'; -import type { Account } from 'sailpoint-api-client'; -import { AccountsApi } from 'sailpoint-api-client'; +import { CommonModule } from '@angular/common'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTableModule } from '@angular/material/table'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatPaginator, MatPaginatorModule, PageEvent } from '@angular/material/paginator'; +import { SailPointSDKService } from '../sailpoint-sdk.service'; +import { AccountV2025 } from 'sailpoint-api-client'; -export const load = async ({ url, locals }) => { - const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token); - const api = new AccountsApi(config); +@Component({ + selector: 'app-accounts', + standalone: true, + imports: [ + CommonModule, + MatButtonModule, + MatCardModule, + MatIconModule, + MatTableModule, + MatToolbarModule, + MatProgressSpinnerModule, + MatPaginatorModule + ], + templateUrl: './accounts.component.html', + styleUrl: './accounts.component.scss', +}) +export class AccountsComponent implements OnInit { + title = 'Accounts'; + loading = true; + accounts: AccountV2025[] = []; + displayedColumns: string[] = ['id', 'name', 'nativeIdentity', 'sourceId', 'disabled', 'locked', 'actions']; + + // Pagination settings + pageSize = 10; + pageIndex = 0; + totalCount = 0; - // highlight-start - const page = getPage(url); - const limit = getLimit(url); - // highlight-end + @ViewChild(MatPaginator) paginator!: MatPaginator; - // highlight-next-line - const reportResp = api.listAccounts({count: true, limit: Number(limit), offset: Number(page) * Number(limit)}); + constructor(private sdk: SailPointSDKService) {} - const totalCount = new Promise((resolve) => { - reportResp.then((response) => { - resolve(response.headers['x-total-count']); - }); - }); + ngOnInit() { + // Load initial data + void this.loadAccounts(); + } - const accountData = new Promise((resolve) => { - reportResp.then((response) => { - resolve(response.data); - }); - }); + async loadAccounts() { + // Setup request for paged account results + const request = { + offset: this.pageIndex * this.pageSize, + limit: this.pageSize, + count: true, + sorters: undefined, + filters: undefined + }; + + this.loading = true; + try { + const response = await this.sdk.listAccounts(request); + this.accounts = response.data; + + // Get total count from headers if available + let count: number | undefined; + if (response.headers && typeof (response.headers as any).get === 'function') { + const headerValue = (response.headers as any).get('X-Total-Count'); + count = headerValue ? Number(headerValue) : undefined; + } else if (response.headers && typeof (response.headers as any)['x-total-count'] !== 'undefined') { + count = Number((response.headers as any)['x-total-count']); + } + + this.totalCount = count ?? 250; // Default to 250 if count not available + console.log('Loaded accounts:', this.accounts); + } catch (error) { + console.error('Error loading accounts:', error); + } finally { + this.loading = false; + } + } + + // Handle page change events + onPageChange(event: PageEvent) { + this.pageSize = event.pageSize; + this.pageIndex = event.pageIndex; + void this.loadAccounts(); + } + + viewAccount(account: AccountV2025): void { + console.log('Viewing account:', account); + } +} - // highlight-next-line - return { accountData, totalCount, params: {page, limit}}; -}; ```
-Return to the accounts list page. You will see the paginator at the top of the page. You can now paginate through the accounts in your tenant. +Return to the accounts list page. You will see the paginator at the bottom of the page. You can now paginate through the accounts in your tenant. + + +## Vewing Details + +In this step we will create a detail view to see the raw json object that represents the underlying data. + +To do this, we will implement the already existing `viewAccount` method to use the `GenericDialogComponent` that comes with the UI Development Kit. The only thing to change here is just to implement the method as seen below: + + + +```typescript + viewAccount(account: AccountV2025): void { + // Format account details as JSON string with indentation + const details = JSON.stringify(account, null, 2); + + // Open dialog with account details + this.dialog.open(GenericDialogComponent, { + minWidth: '800px', + data: { + title: `Account Details: ${account.name || account.nativeIdentity || account.id}`, + message: details + } + }); + } +``` + +Note that we also need to add the dialog to the constructor: + +```typescript + constructor(private sdk: SailPointSDKService) {} +``` + +And also add the imports: + +``` +import { MatDialog } from '@angular/material/dialog'; +import { GenericDialogComponent } from '../generic-dialog/generic-dialog.component'; +``` ## Sort and filter -To better view and organize the data displayed in your accounts list page, you may want to implement sorting and filtering. -Sorting is the process of organizing the data. You may want to provide users with a way to sort the data in ascending or descending alphabetical order based on the account name, for example. -Filtering is the process of limiting the displayed data based on specified details. You may want to provide users with a way to filter the data to only include accounts associated with one source, for example. +The last part of the page we may want to implement would be sorting and filtering. +With this implementation, the `this.filterForm.valueChanges` event and `onSortChange` event will cause the page to reload the accounts with the new sort or filter applied. -To implement sorting and filtering, add the following highlighted code. It allows you to sort and filter the accounts in your tenant: - -With this implementation, once a user types in a filter or sorter and clicks the 'Go' button, the UI Development Kit calls the `onCreateGo` function and reloads the page with the new sorters and filters. - -On the server side, the kit uses the `getFilters` and `getSorters` functions to get the filters and sorters from the URL. -The kit then passes these functions to the [List Accounts endpoint](https://developer.sailpoint.com/docs/api/v3/list-accounts) to filter and sort the accounts. +The completed component and all code can be seen below: - + -```typescript - - -
-
-

List of all accounts

-
- {#await data.accountData} -
- +
+ +
+
+
+ + Name + + + + + Source ID + + + + + Correlated + + + {{option.label}} + + + + +
- {:then accountData} - {#await data.totalCount then totalCount} - {#if totalCount > 250 || Number(data.params.limit) < totalCount} -
- -
- {/if} - {/await} - {#if accountData.length === 0} -
-

No Accounts found

-
- {:else} -
- - - - - - - - - - - - - - {#each accountData as account} - - - - - - - - - - - - {/each} - -
Name Native Identity Source Created Modified Authoritative Features Has Entitlements
- {account.name} - - {account.nativeIdentity} - - {account.sourceName} - - {formatDate(account.created)} - - {formatDate(account.modified)} - - {account.authoritative} - - {account.features} - - {account.hasEntitlements} - -
- -
-
-
- {/if} - {/await} +
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID{{ account.id }}Name{{ account.name || '-' }}Native Identity{{ account.nativeIdentity || '-' }}Source{{ account.sourceId || '-' }}Disabled + check_circle + cancel + Locked + lock + lock_open + Actions + +
+ + + + + + +
+ No accounts found. +
+
+
+ + ``` - + ```typescript -import { createConfiguration } from '$lib/sailpoint/sdk.js'; -// highlight-next-line -import { getFilters, getLimit, getPage, getSorters } from '$lib/Utils.js'; -import type { Account } from 'sailpoint-api-client'; -import { AccountsApi } from 'sailpoint-api-client'; +import { CommonModule } from '@angular/common'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatSort, MatSortModule, Sort, SortDirection } from '@angular/material/sort'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatDialog } from '@angular/material/dialog'; +import { GenericDialogComponent } from '../generic-dialog/generic-dialog.component'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTableModule } from '@angular/material/table'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatPaginator, MatPaginatorModule, PageEvent } from '@angular/material/paginator'; +import { SailPointSDKService } from '../sailpoint-sdk.service'; +import { AccountV2025 } from 'sailpoint-api-client'; -export const load = async ({ url, locals }) => { - const config = createConfiguration(locals.session!.baseUrl, locals.idnSession!.access_token); - const api = new AccountsApi(config); +@Component({ + selector: 'app-accounts', + standalone: true, + imports: [ + CommonModule, + MatButtonModule, + MatCardModule, + MatIconModule, + MatTableModule, + MatToolbarModule, + MatProgressSpinnerModule, + MatPaginatorModule, + MatSortModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + GenericDialogComponent + ], + templateUrl: './accounts.component.html', + styleUrl: './accounts.component.scss', +}) +export class AccountsComponent implements OnInit { + title = 'Accounts'; + loading = true; + accounts: AccountV2025[] = []; + error = false; + errorMessage = ''; + displayedColumns: string[] = ['id', 'name', 'nativeIdentity', 'sourceId', 'disabled', 'locked', 'actions']; - const page = getPage(url); - const limit = getLimit(url); - // highlight-start - const sorters = getSorters(url); - const filters = getFilters(url); - // highlight-end + // Sort settings + sortActive = 'name'; + sortDirection: SortDirection = 'asc'; - // highlight-next-line - const reportResp = api.listAccounts({count: true, sorters: sorters, filters: filters, limit: Number(limit), offset: Number(page) * Number(limit)}); + // Filter form + filterForm = new FormGroup({ + name: new FormControl(''), + sourceId: new FormControl(''), + correlated: new FormControl('') + }); - const totalCount = new Promise((resolve) => { - reportResp.then((response) => { - resolve(response.headers['x-total-count']); - }); + // Filter options + correlatedOptions = [ + { value: '', label: 'All' }, + { value: 'true', label: 'Correlated' }, + { value: 'false', label: 'Uncorrelated' } + ]; + + // Pagination settings + pageSize = 10; + pageIndex = 0; + totalCount = 0; + + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; + + constructor(private sdk: SailPointSDKService, private dialog: MatDialog) {} + + ngOnInit() { + // Load initial data + void this.loadAccounts(); + + // Subscribe to filter changes + this.filterForm.valueChanges.subscribe(() => { + this.pageIndex = 0; // Reset to first page on filter change + void this.loadAccounts(); }); + } - const accountData = new Promise((resolve) => { - reportResp.then((response) => { - resolve(response.data); - }); + async loadAccounts() { + // Setup request for paged account results + const request = { + offset: this.pageIndex * this.pageSize, + limit: this.pageSize, + count: true, + sorters: this.buildSorters(), + filters: this.buildFilters() + }; + + this.loading = true; + this.error = false; + this.errorMessage = ''; + + try { + const response = await this.sdk.listAccounts(request); + if (response.status !== 200) { + throw new Error(`Failed to load accounts: ${response.statusText}`); + } + this.accounts = response.data; + + // Get total count from headers if available + let count: number | undefined; + if (response.headers && typeof (response.headers as any).get === 'function') { + const headerValue = (response.headers as any).get('X-Total-Count'); + count = headerValue ? Number(headerValue) : undefined; + } else if (response.headers && typeof (response.headers as any)['x-total-count'] !== 'undefined') { + count = Number((response.headers as any)['x-total-count']); + } + + this.totalCount = count ?? 250; // Default to 250 if count not available + } catch (error) { + console.error('Error loading accounts:', error); + this.error = true; + this.errorMessage = error instanceof Error ? error.message : String(error); + this.accounts = []; + } finally { + this.loading = false; + } + } + + // Handle page change events + onPageChange(event: PageEvent) { + this.pageSize = event.pageSize; + this.pageIndex = event.pageIndex; + void this.loadAccounts(); + } + + // Handle sort changes + onSortChange(event: Sort) { + this.sortActive = event.active; + this.sortDirection = event.direction as SortDirection; + void this.loadAccounts(); + } + + // Reset filters + resetFilters() { + this.filterForm.reset({ + name: '', + sourceId: '', + correlated: '' }); + } - // highlight-next-line - return { accountData, totalCount, params: {page, limit, sorters, filters}}; -}; + // Build sorters string for API request + buildSorters(): string | undefined { + if (!this.sortActive || this.sortDirection === '') { + return undefined; + } + // For descending order, prefix column name with minus sign + return this.sortDirection === 'desc' ? `-${this.sortActive}` : this.sortActive; + } + + // Build filters string for API request + buildFilters(): string | undefined { + const filters: string[] = []; + const formValues = this.filterForm.value; + + if (formValues.name) { + filters.push(`name sw "${formValues.name}"`); + } + + if (formValues.sourceId) { + filters.push(`sourceId eq "${formValues.sourceId}"`); + } + + if (formValues.correlated) { + filters.push(`identity.correlated eq ${formValues.correlated}`); + } + + return filters.length > 0 ? filters.join(' and ') : undefined; + } + + viewAccount(account: AccountV2025): void { + // Format account details as JSON string with indentation + const details = JSON.stringify(account, null, 2); + + // Open dialog with account details + this.dialog.open(GenericDialogComponent, { + minWidth: '800px', + data: { + title: `Account Details: ${account.name || account.nativeIdentity || account.id}`, + message: details + } + }); + } +} + + + +``` + + + + + +```css +.accounts-container { + height: 100%; + display: flex; + flex-direction: column; +} + +.table-container { + display: flex; + flex-direction: column; +} + +.toolbar-title { + margin-left: 16px; +} + +.content { + padding: 24px; + flex: 1; + overflow-y: auto; +} + +.filter-panel { + margin-bottom: 20px; + padding: 16px; + border-radius: 4px; + background-color: #f8f8f8; +} + +.filter-row { + display: flex; + flex-wrap: wrap; + gap: 16px; + align-items: center; +} + +.filter-field { + flex: 1; + min-width: 200px; +} + +:host-context(.dark-theme) .filter-panel { + background-color: #333; +} + +mat-card { + max-width: 800px; + margin: 0 auto; +} + +mat-card-actions { + display: flex; + gap: 8px; + padding: 16px; +} + +mat-card-actions button { + display: flex; + align-items: center; + gap: 8px; +} + +::ng-deep mat-spinner circle { + stroke: #0033a1; /* teal-like custom color */ +} + +.spinner-container { + display: flex; + justify-content: center; // horizontal centering + align-items: center; // vertical centering + border: none; + height: 75vh; // takes full viewport height (adjust as needed) +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.sortable { + cursor: pointer; +} + +.sortable:hover { + background-color: #f3f3f3; +} + +.sort-icon { + margin-left: 6px; + font-size: 0.95rem; + color: black; + + // Highlight active sort + &.active { + color: #415364; + font-weight: bold; + } +} +td.mat-cell, +th.mat-header-cell { + vertical-align: middle; +} + +td.mat-cell:last-child, +th.mat-header-cell:last-child { + text-align: center; +} + +#viewIdentity, +#attibuteDetails, +#managerDetails { + padding: 8px; + margin-bottom: 10px; + margin-top: 10px; + width: 55px; +} + +#attibuteDetails, +#managerDetails { + width: 125px; +} + +:host { + /* Dark mode overrides */ + + :host-context(.dark-theme) .sortable:hover { + background-color: #2c2c2c; /* Darker hover background */ + } + + :host-context(.dark-theme) .sort-icon { + margin-left: 6px; + font-size: 0.95rem; + color: #ffffff; + + // Highlight active sort + &.active { + color: #ffffff; + font-weight: bold; + } + } +} ``` + + + ## Error handling You have now implemented a new page that lists all the accounts in your tenant, and you can now paginate, sort and filter the accounts in your tenant. -Ideally, everything in your custom UIs will work smoothly, but you will likely encounter errors at some point when you're implementing a page. If you provide an invalid filter or sorter, the list accounts endpoint will return a 400 error, for example. -You can handle this error by adding a `try catch` block to the server side of the accounts list page. +Ideally, everything in your custom UIs will work smoothly, but you will likely encounter errors at some point when you're implementing a page. For example, if you provide an invalid filter or sorter, the list accounts endpoint will return a 400 error. +You can see that there is a `try catch` block on the loadAccounts method that currently shows a console.log error but a custom message could be implemented to notify the user of a problem. This is not covered in this part of the tutorial, but with angular, presenting the user about an error is quite trivial. To learn more about handling errors in your UI, refer to [Error Handling](./error-handling). diff --git a/docs/tools/ui-development-kit/deploying.md b/docs/tools/ui-development-kit/deploying.md new file mode 100644 index 000000000..b597f67b0 --- /dev/null +++ b/docs/tools/ui-development-kit/deploying.md @@ -0,0 +1,45 @@ +--- +id: udk-deploying +title: Deploying +pagination_label: UDK +sidebar_label: Deploying +sidebar_position: 2 +sidebar_class_name: rudk +keywords: ['UI', 'development', 'kit'] +description: Deploying your UI project +slug: /tools/ui-development-kit/deploying +tags: ['UI'] +--- + +## Building the App + +To build the app locally, you can simply run the command `npm run electron:build` and the app will be built for your platform. The location of the built file can be found in the `./release` folder. + +### Building for different platforms + +If you want to build a release that can be used on other platforms, then you can use the github workflows to accomplish this task. By forking the repository, you will also have access to the github actions in the .github/workflows directory and there is an action for macos, linux and windows that can be used to build the application. Upon a successful build, the built executable is stored in github as a resource. + +### Building with customization + +Inside the `app/config.json` are the default navigation menu items that will presented to a user when they run the app. You can modify these values so that the bundled application will only allow the user to access certain pre-built components. That way if you want to bundle a purpose built app that only has access to the transform builder, you can do that by modifying the `app/config.json` like this: + +```json +{ + "components": { + "enabled": [ + "transforms" + ] + }, + "version": "1.0.0" + } +``` + +In this case the user won't even have access to the component selector to enable other components, so the app will be limited to only the transform tool. You can also adjust the custom themes for the app using the `config.json` as well, to see more on this, see [theming](./theming) + +Once the app is started, the user settings will be stored in the users `appData/Roaming/sailpoint-ui-development-kit` folder and can be modified there to allow access to other components if desired. + +:::info + +The UI Development Kit uses the existing configuration stored in the users data directory. If there is a pre-installed instance of the app or the config.json file already exists, it will not be overwritten by the application and their current configuration will be used. + +::: \ No newline at end of file diff --git a/docs/tools/ui-development-kit/error-handling.md b/docs/tools/ui-development-kit/error-handling.md index 5457dd6f0..0f8408b1f 100644 --- a/docs/tools/ui-development-kit/error-handling.md +++ b/docs/tools/ui-development-kit/error-handling.md @@ -11,128 +11,54 @@ slug: /tools/ui-development-kit/error-handling tags: ['UI', 'Error'] --- -Ideally, everything in your custom UIs will work smoothly, but you will likely encounter errors at some point when you're implementing a page. If you provide an invalid filter or sorter, the list accounts endpoint will return a 400 error, for example. You can handle this error by adding a `try catch` block to the server side of the accounts list page. +Ideally, everything in your custom UIs will work smoothly, but you will likely encounter errors at some point when you're implementing a page. For example, if you provide an invalid filter or sorter, the list accounts endpoint will return a 400 error. Even though the actual calls happen in the backend electron code, you almost never need to interact with that part of the application. Instead, it is best to handle all the errors on the front end If any of your backend calls result in a server error or bad request, you also want to handle those errors. Read this guide to learn how to use the UI Development Kit to handle errors. -## 400 bad request +## Handling errors in your code -If you provide an invalid filter or sorter, the [List Accounts Endpoint](https://developer.sailpoint.com/docs/api/v3/list-accounts) returns a 400 error. This example awaits the response and doesn't exit the program when a 4xx level status is received. If a 4xx level status is received, the user is redirected to an error page. +If you provide an invalid filter or sorter, the [List Accounts Endpoint](https://developer.sailpoint.com/docs/api/v2025/list-accounts) returns a 400 error. This example uses a try/catch to handle the error and present the user with what went wrong. -Refer to this code block to learn how to implement error handling for invalid filters or sorters: +This is just one example of how things can be handled during an error event. Sometimes it can be better to present the user with a popup, or you can use a snackbar to show a quick popup on the bottom of the page. ```typescript -import {createConfiguration} from '$lib/sailpoint/sdk.js'; -import {getFilters, getLimit, getPage, getSorters} from '$lib/Utils.js'; -import {error} from '@sveltejs/kit'; -import {AccountsApi} from 'sailpoint-api-client'; -export const load = async ({url, locals}) => { - const config = createConfiguration( - locals.session!.baseUrl, - locals.idnSession!.access_token, - ); - const api = new AccountsApi(config); + // initially, set error to false and have no error message + this.error = false; + this.errorMessage = ''; - const page = getPage(url); - const limit = getLimit(url); - const sorters = getSorters(url); - const filters = getFilters(url); - - const reportResp = await api.listAccounts( - { - count: true, - sorters: sorters, - filters: filters, - limit: Number(limit), - offset: Number(page) * Number(limit), - }, - { - validateStatus: function (status) { - return status < 500; - }, - }, - ); - - if (reportResp.status !== 200) { - error(400, { - message: - 'an error occurred while fetching accounts. Please examine your filters and and sorters and try again.', - context: {params: {page, limit, filters, sorters}}, - urls: [ - 'https://developer.sailpoint.com/idn/api/standard-collection-parameters#filtering-results', - ], - errData: reportResp.data, - }); - } - - const totalCount = reportResp.headers['x-total-count']; - const accountData = reportResp.data; - - return {accountData, totalCount, params: {page, limit, sorters, filters}}; -}; + try { + const response = await this.sdk.listAccounts(request); + if (response.status !== 200) { // check to see if the response code is the expected value. In case it's not, just throw an error as it will be handled in the try catch block + throw new Error(`Failed to load accounts: ${response.statusText}`); + } + this.accounts = response.data; + } catch (error) { // In case the SDK encounters some error in the request + console.error('Error loading accounts:', error); + this.error = true; + this.errorMessage = error instanceof Error ? error.message : String(error); + this.accounts = []; + } finally { + this.loading = false; + } ``` -## 500 server issues +Present the user with the error message if there is an error present during loading any data +```html +
+ error + Error loading accounts: {{ errorMessage }} +
+``` -You can update the code block to handle more than just the 400 level statuses. You can see the highlighted code changes to handle any error response from the API call. You can send back an error to the user with the status, a detailed message, the details about the parameters used that caused the error, and the error response from the API. +## Retrieving logs from the app -Refer to this code block to learn how to implement error handling for other non-400 errors: +When running the built app locally, you can retrieve details logging from the app by running the executable with the `--enable-logging` flag enabled. For example: -```typescript -import {createConfiguration} from '$lib/sailpoint/sdk.js'; -import {getFilters, getLimit, getPage, getSorters} from '$lib/Utils.js'; -import {error} from '@sveltejs/kit'; -import {AccountsApi} from 'sailpoint-api-client'; - -export const load = async ({url, locals}) => { - const config = createConfiguration( - locals.session!.baseUrl, - locals.idnSession!.access_token, - ); - const api = new AccountsApi(config); - - const page = getPage(url); - const limit = getLimit(url); - const sorters = getSorters(url); - const filters = getFilters(url); - - const reportResp = await api.listAccounts( - { - count: true, - sorters: sorters, - filters: filters, - limit: Number(limit), - offset: Number(page) * Number(limit), - }, - { - validateStatus: function (status) { - // highlight-next-line - return status < 550; - }, - }, - ); - - if (reportResp.status !== 200) { - // highlight-next-line - error(reportResp.status, { - message: - 'an error occurred while fetching accounts. Please examine your filters and and sorters and try again.', - context: {params: {page, limit, filters, sorters}}, - urls: [ - 'https://developer.sailpoint.com/idn/api/standard-collection-parameters#filtering-results', - ], - errData: reportResp.data, - }); - } - - const totalCount = reportResp.headers['x-total-count']; - const accountData = reportResp.data; - - return {accountData, totalCount, params: {page, limit, sorters, filters}}; -}; +```powershell + & '.\sailpoint-ui-development-kit 1.0.0.exe' --enable-logging ``` ## Discuss diff --git a/docs/tools/ui-development-kit/img/theme-component.png b/docs/tools/ui-development-kit/img/theme-component.png new file mode 100644 index 0000000000000000000000000000000000000000..4c418c09ba2d462ec9a5f5ad428e822d6539761d GIT binary patch literal 68343 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

*WYuC0I!X04JD`rzY-4bJfnJkt^Fy+ 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 UG5iVm#~_od*A1(#-2U@_08+Op@&Et; literal 0 HcmV?d00001 diff --git a/docs/tools/ui-development-kit/index.md b/docs/tools/ui-development-kit/index.md index e84e780f3..493327978 100644 --- a/docs/tools/ui-development-kit/index.md +++ b/docs/tools/ui-development-kit/index.md @@ -46,7 +46,7 @@ The first step to setting up the UI Development Kit is to clone the project from To clone the project, you can run this command: ```bash -git clone git@github.com:sailpoint-oss/ui-development-kit.git +git clone https://github.com/sailpoint-oss/ui-development-kit.git ``` ## Project structure diff --git a/docs/tools/ui-development-kit/theming.md b/docs/tools/ui-development-kit/theming.md index 835f49b02..e99463319 100644 --- a/docs/tools/ui-development-kit/theming.md +++ b/docs/tools/ui-development-kit/theming.md @@ -11,4 +11,12 @@ slug: /tools/ui-development-kit/theming tags: ['UI'] --- -Read this guide to learn about the UI Development Kit and how to use it. Once you have read this guide, you will be able to do the following: +If you want to create a custom theme for your deployment, you can do it easily using the config file and the theme config component: + +## Using the theme component + +Using the theme component, you can select colors for all the main objects on the UI such as the primary and secondary colors, as well as pick which logos will appear for dark and light mode. Once you have these changes made, the settings will be saved in your `appData/Roaming/sailpoint-ui-development-kit` and the app can be deployed with these settings deployed along with the app by using the [deployment guide](./deploying.) + +## Example Theme Component + +![Theme Component](./img/theme-component.png) \ No newline at end of file