Functional API Client

This commit is contained in:
Luke Hagar
2023-05-17 14:56:52 -05:00
parent 6dc951c301
commit dac5b5cbe9
8 changed files with 170 additions and 31 deletions

View File

@@ -4,7 +4,6 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"prebuild": "curl https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.v3.yaml -o src/lib/V3Spec.yaml && curl https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.beta.yaml -o src/lib/BetaSpec.yaml && yq -o=json eval src/lib/V3Spec.yaml > src/routes/api-client/V3Spec.json && yq -o=json eval src/lib/BetaSpec.yaml > src/routes/api-client/BetaSpec.json",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@@ -27,6 +26,7 @@
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.45.0",
"algoliasearch": "^4.17.0", "algoliasearch": "^4.17.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",

54
pnpm-lock.yaml generated
View File

@@ -43,6 +43,9 @@ devDependencies:
autoprefixer: autoprefixer:
specifier: ^10.4.14 specifier: ^10.4.14
version: 10.4.14(postcss@8.4.23) version: 10.4.14(postcss@8.4.23)
axios:
specifier: ^1.4.0
version: 1.4.0
dayjs: dayjs:
specifier: ^1.11.7 specifier: ^1.11.7
version: 1.11.7 version: 1.11.7
@@ -1141,6 +1144,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
/autoprefixer@10.4.14(postcss@8.4.23): /autoprefixer@10.4.14(postcss@8.4.23):
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -1172,6 +1179,16 @@ packages:
- debug - debug
dev: true dev: true
/axios@1.4.0:
resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==}
dependencies:
follow-redirects: 1.15.2
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: true
/balanced-match@1.0.2: /balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true dev: true
@@ -1311,6 +1328,13 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true dev: true
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/commander@4.1.1: /commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -1384,6 +1408,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: true
/detect-indent@6.1.0: /detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1730,6 +1759,15 @@ packages:
optional: true optional: true
dev: true dev: true
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/fraction.js@4.2.0: /fraction.js@4.2.0:
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
dev: true dev: true
@@ -2102,6 +2140,18 @@ packages:
picomatch: 2.3.1 picomatch: 2.3.1
dev: true dev: true
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: true
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: true
/mime@3.0.0: /mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@@ -2441,6 +2491,10 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: true
/punycode@2.3.0: /punycode@2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'} engines: {node: '>=6'}

View File

@@ -5,7 +5,7 @@
</script> </script>
<div class="p-2 card variant-soft-surface min-w-[150px]"> <div class="p-2 card variant-soft-surface min-w-[150px]">
<p class="underline text-center pb-2">Current User</p> <h1 class="text-center pb-2">Current User</h1>
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<p class="text-sm">Account:</p> <p class="text-sm">Account:</p>
<p class="text-sm">{$tenantData.uid}</p> <p class="text-sm">{$tenantData.uid}</p>

View File

@@ -5,7 +5,7 @@
</script> </script>
<div class="p-2 card variant-soft-surface min-w-[150px]"> <div class="p-2 card variant-soft-surface min-w-[150px]">
<p class="underline text-center pb-2">Hosting Data</p> <h1 class="text-center pb-2">Hosting Data</h1>
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<p class="text-sm">Org:</p> <p class="text-sm">Org:</p>
<p class="text-sm">{$hostingData.org}</p> <p class="text-sm">{$hostingData.org}</p>

View File

@@ -16,7 +16,7 @@
</script> </script>
<div class="p-2 card variant-soft-surface min-w-[183.53px]"> <div class="p-2 card variant-soft-surface min-w-[183.53px]">
<p class="underline text-center pb-2">Status Page</p> <h1 class="text-center pb-2">Status Page</h1>
<div class="flex flex-row gap-2 justify-center"> <div class="flex flex-row gap-2 justify-center">
{#await resp} {#await resp}
<div class="placeholder-circle w-16" /> <div class="placeholder-circle w-16" />

View File

@@ -15,7 +15,7 @@
</script> </script>
<div class="p-2 card variant-soft-surface"> <div class="p-2 card variant-soft-surface">
<p class="underline text-center pb-2">Support</p> <h1 class="text-center pb-2">Support</h1>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{#each links as link} {#each links as link}
<a class="btn btn-sm variant-filled" target="_blank" rel="noreferrer" href={link.href}> <a class="btn btn-sm variant-filled" target="_blank" rel="noreferrer" href={link.href}>

View File

@@ -5,7 +5,7 @@
</script> </script>
<div class="p-2 card variant-soft-surface min-w-[150px]"> <div class="p-2 card variant-soft-surface min-w-[150px]">
<p class="underline text-center pb-2">Tenant Data</p> <h1 class="text-center pb-2">Tenant Data</h1>
<div class="flex flex-row flex-wrap justify-between"> <div class="flex flex-row flex-wrap justify-between">
<p class="text-sm">Name:</p> <p class="text-sm">Name:</p>
<p class="text-sm px-1">{$tenantData.org.name}</p> <p class="text-sm px-1">{$tenantData.org.name}</p>

View File

@@ -1,63 +1,148 @@
<script lang="ts"> <script lang="ts">
import { idnSession } from '$lib/settings';
import { Tab, TabGroup } from '@skeletonlabs/skeleton'; import { Tab, TabGroup } from '@skeletonlabs/skeleton';
import { JSONEditor } from 'svelte-jsoneditor'; import { JSONEditor } from 'svelte-jsoneditor';
import 'svelte-jsoneditor/themes/jse-theme-dark.css'; import 'svelte-jsoneditor/themes/jse-theme-dark.css';
import { paths as V2Spec } from './IdentityNow-V2-APIReference.json'; import { paths as V2Spec } from './BetaSpec.json';
import { paths as BetaSpec } from './IdentityNow-Beta-APIReference.json'; import { paths as BetaSpec } from './V3Spec.json';
import { paths as V3Spec } from './IdentityNow-V3-APIReference.json'; import { paths as V3Spec } from './V2Spec.json';
import { paths as CCSpec } from './Identitynow-CC-APIReference.json'; import { paths as CCSpec } from './CCSpec.json';
let APIVersions; import axios, { type AxiosResponse } from 'axios';
let specification = BetaSpec; function mapPath(path: string[]) {
let path = Object.entries(specification.paths)[0][0]; const [name, value] = path;
return { name, value };
}
async function makeAPICall() {
const response = await axios({
method: selectedAPIMethod,
url: `${$idnSession.baseUrl}/${APICallPath}`,
data: requestContent.json,
headers: {
authorization: `Bearer ${$idnSession.accessToken}`
}
}).catch((err) => {
console.error(err);
return err;
});
responseContent = { text: undefined, json: response.data };
console.log(responseContent);
}
function handleKeydown(event: KeyboardEvent) {
console.log(event);
if (event.isTrusted === true && event.key === 'Enter') {
makeAPICall();
}
}
let APIVersions = [
//@ts-ignore
{ name: 'Beta', value: Object.entries(BetaSpec).map((path) => mapPath(path)) },
//@ts-ignore
{ name: 'V3', value: Object.entries(V3Spec).map((path) => mapPath(path)) },
//@ts-ignore
{ name: 'V2', value: Object.entries(V2Spec).map((path) => mapPath(path)) },
//@ts-ignore
{ name: 'CC', value: Object.entries(CCSpec).map((path) => mapPath(path)) },
{
name: 'Custom',
value: [
{
name: 'Custom Path',
value: { GET: '', POST: '', PUT: '', PATCH: '', DELETE: '', HEAD: '' }
}
]
}
];
let requestContent = { let requestContent = {
text: undefined, // can be used to pass a stringified JSON document instead text: undefined, // can be used to pass a stringified JSON document instead
json: { json: ''
request: 'All your base belong to us'
}
}; };
let responseContent = { let responseContent = {
text: undefined, // can be used to pass a stringified JSON document instead text: undefined, // can be used to pass a stringified JSON document instead
json: { json: ''
response: 200
}
}; };
let tabSet: number = 0; let tabSet: number = 1;
$: methods = Object.entries(specification.paths[path] || { get: 'content' }); let selectedAPIVersion = APIVersions[0];
let selectedPath = selectedAPIVersion.value[0];
let APICallPath: string = `${selectedAPIVersion.name.toLowerCase()}${selectedPath.name}`;
let selectedAPIMethod = 'GET';
</script> </script>
<div class="p-4 flex flex-col gap-4 h-full"> <div class="p-4 flex flex-col gap-4 h-full">
<div class="flex flex-row"> <div class="flex flex-row">
<select bind:value={specification} class="select w-fit rounded-r-none"> <select
<option value={BetaSpec}>Beta API</option> placeholder="Select an API Version"
<option value={V3Spec}>V3 API</option> class="max-w-fit !rounded-r-none px-4 py-2 select"
<option value={CustomSpec}>Custom</option> bind:value={selectedAPIVersion}
on:change={() => {
selectedPath = selectedAPIVersion.value[0];
if (['Beta', 'V3', 'V2'].includes(selectedAPIVersion.name)) {
APICallPath = `${selectedAPIVersion.name.toLowerCase()}${selectedPath.name}`;
} else if (['CC'].includes(selectedAPIVersion.name)) {
APICallPath = `${selectedPath.name}`;
} else {
APICallPath = '';
}
}}
>
{#each APIVersions as APIVersion}
<option selected={selectedAPIVersion === APIVersion} value={APIVersion}>
{APIVersion.name}
</option>
{/each}
</select> </select>
<select bind:value={path} class="select rounded-l-none"> <select
{#each Object.entries(specification.paths) as [path, methods]} placeholder="Choose the API Endpoint"
<option value={path}>{path}</option> class="!rounded-l-none px-4 select"
bind:value={selectedPath}
on:change={() => {
if (['Beta', 'V3', 'V2'].includes(selectedAPIVersion.name)) {
APICallPath = `${selectedAPIVersion.name.toLowerCase()}${selectedPath.name}`;
} else if (['CC'].includes(selectedAPIVersion.name)) {
APICallPath = `${selectedPath.name}`;
} else {
APICallPath = '';
}
}}
>
{#each selectedAPIVersion.value as path}
<option selected={path === selectedPath} value={path}>{path.name}</option>
{/each} {/each}
</select> </select>
</div> </div>
<div class="flex flex-row"> <div class="flex flex-row">
<select class="select rounded-r-none w-fit"> <select class="select rounded-r-none w-fit">
{#each methods as [method, content]} {#each Object.entries(selectedPath.value) as [method, content]}
<option>{method.toUpperCase()}</option> <option>{method.toUpperCase()}</option>
{/each} {/each}
</select> </select>
<input bind:value={path} class="input rounded-l-none rounded-r-none pl-4" /> <input
<button class="btn variant-filled-surface rounded-l-none rounded-r-lg">Call</button> type="text"
class="w-full !rounded-l-none rounded-r-none px-4 py-2 input"
bind:value={APICallPath}
/>
<button on:click={makeAPICall} class="btn variant-filled-surface rounded-l-none rounded-r-lg">
Call
</button>
</div> </div>
<TabGroup justify="justify-center" class="grow"> <TabGroup justify="justify-center" class="grow">
<Tab bind:group={tabSet} name="tab1" value={0}>Request</Tab> <Tab bind:group={tabSet} name="tab1" value={0}>Request</Tab>
<Tab bind:group={tabSet} name="tab2" value={1}>Response</Tab> <Tab bind:group={tabSet} name="tab2" value={1}>Response</Tab>
</TabGroup> </TabGroup>
{#if tabSet === 0} {#if tabSet === 0}
<div class="jse-theme-dark h-full"> <div class="jse-theme-dark h-full">
<JSONEditor bind:content={requestContent} /> <JSONEditor bind:content={requestContent} />