Resolve merge conflicts
53
.github/workflows/build-and-deploy-stg-gt-pages.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Build/Deploy to AWS S3 Staging
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ['stage']
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
- '.github/**'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: 'aws'
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BASE_URL: '/'
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v3
|
||||
# Node is required for npm
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
# Install and build Developer Community site
|
||||
- name: Build Developer Community site
|
||||
run: |
|
||||
export NODE_OPTIONS="--max_old_space_size=4096"
|
||||
npm ci
|
||||
npm run gen-api-docs-all
|
||||
npm run build
|
||||
- name: Configure AWS credentials from Test account
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{secrets.SITE_AWS_ACCESS_KEY_ID}}
|
||||
aws-secret-access-key: ${{secrets.SITE_AWS_SECRET_ACCESS_KEY}}
|
||||
aws-region: us-east-1
|
||||
|
||||
# push these files to AWS
|
||||
- name: Copy files to the test website with the AWS CLI
|
||||
run: |
|
||||
aws s3 sync ./build s3://spt-developer
|
||||
|
||||
4
.github/workflows/checkmarx-scan-on-pull.yml
vendored
@@ -17,7 +17,9 @@
|
||||
|
||||
|
||||
name: CheckMarx Scan on Pull Request
|
||||
on: pull_request
|
||||
on:
|
||||
#pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
start-runner:
|
||||
|
||||
@@ -11,7 +11,11 @@ jobs:
|
||||
NODE_ENV: 'development'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: npm ci && npm run gen-api-docs-all && npm run build
|
||||
- run: |
|
||||
export NODE_OPTIONS="--max_old_space_size=4096"
|
||||
npm ci
|
||||
npm run gen-api-docs-all
|
||||
npm run build
|
||||
- uses: FirebaseExtended/action-hosting-deploy@v0
|
||||
with:
|
||||
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||
|
||||
@@ -26,6 +26,17 @@
|
||||
"tags": ["IDN Documentation"]
|
||||
},
|
||||
|
||||
{
|
||||
"url": "https://developer.sailpoint.com/idn/tools/cli",
|
||||
"tags": ["IDN Tools", "CLI"],
|
||||
"selectors_key": "tools"
|
||||
},
|
||||
{
|
||||
"url": "https://developer.sailpoint.com/idn/tools/sdk",
|
||||
"tags": ["IDN Tools", "SDKs"],
|
||||
"selectors_key": "tools"
|
||||
},
|
||||
|
||||
{
|
||||
"url": "https://developer.sailpoint.com/idn/api/getting-started",
|
||||
"selectors_key": "api_v3",
|
||||
@@ -89,6 +100,26 @@
|
||||
"lvl7": "article h6",
|
||||
"text": "article p, article li, article td:last-child"
|
||||
},
|
||||
"tools": {
|
||||
"lvl0": {
|
||||
"selector": "(//ul[contains(@class,'menu__list')]//a[contains(@class, 'menu__link menu__link--sublist menu__link--active')]/text() | //nav[contains(@class, 'navbar')]//a[contains(@class, 'navbar__link--active')]/text())[1]",
|
||||
"type": "xpath",
|
||||
"global": true,
|
||||
"default_value": "IDN Tools"
|
||||
},
|
||||
"lvl1": {
|
||||
"selector": "(//ul[contains(@class,'menu__list')]//a[contains(@class, 'menu__link menu__link--sublist menu__link--active')]/text() | //nav[contains(@class, 'navbar')]//a[contains(@class, 'navbar__link--active')]/text())[last()]",
|
||||
"type": "xpath",
|
||||
"global": true,
|
||||
"default_value": "IDN Tools"
|
||||
},
|
||||
"lvl2": "article h2",
|
||||
"lvl3": "article h3",
|
||||
"lvl4": "article h4",
|
||||
"lvl5": "article h5, article td:first-child",
|
||||
"lvl6": "article h6",
|
||||
"text": "article p, article li, article td:last-child"
|
||||
},
|
||||
"api_v3": {
|
||||
"lvl0": {
|
||||
"selector": "(//ul[contains(@class,'menu__list')]//a[contains(@class, 'menu__link menu__link--sublist menu__link--active')]/text() | //nav[contains(@class, 'navbar')]//a[contains(@class, 'navbar__link--active')]/text())[1]",
|
||||
|
||||
65
archive/.devdaysbak/_agenda.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import {getAgenda, getSpeaker} from '../../services/StreamService';
|
||||
import {flushSync} from 'react-dom';
|
||||
import AgendaContent from '../../components/agenda/agenda';
|
||||
|
||||
export default function Agenda() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
const [agenda, setAgenda] = useState({
|
||||
day1: [],
|
||||
day2: [],
|
||||
day3: [],
|
||||
});
|
||||
const [speakers, setSpeakers] = useState([]);
|
||||
const [filterSelection, setFilterSelection] = React.useState('IDN');
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const dates = {
|
||||
day1: new Date('2023-03-07').toLocaleDateString([], {dateStyle: 'full'}),
|
||||
day2: new Date('2023-03-08').toLocaleDateString([], {dateStyle: 'full'}),
|
||||
day3: new Date('2023-03-09').toLocaleDateString([], {dateStyle: 'full'}),
|
||||
};
|
||||
|
||||
function formatSpeaker(id) {
|
||||
return speakers?.filter((spkr) => spkr.id === id)[0];
|
||||
}
|
||||
|
||||
function diff_minutes(dt2, dt1) {
|
||||
var diff = (dt2.getTime() - dt1.getTime()) / 1000;
|
||||
diff /= 60;
|
||||
return Math.abs(Math.round(diff));
|
||||
}
|
||||
|
||||
const sessionFilter = (obj) => {
|
||||
if (obj.hidden === true) return false;
|
||||
return obj.stage === filterSelection;
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
const tempAgenda = await getAgenda();
|
||||
const tempSpeakers = await getSpeaker();
|
||||
setAgenda(tempAgenda);
|
||||
setSpeakers(tempSpeakers);
|
||||
console.log(tempAgenda);
|
||||
console.log(tempSpeakers);
|
||||
setLoading(false);
|
||||
console.log('Done Loading');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<main>
|
||||
<div className="p-2 lg:px-28 my-24">
|
||||
<p className="text-3xl text-center">Agenda</p>
|
||||
<AgendaContent
|
||||
agenda={agenda}
|
||||
speakers={speakers}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
111
archive/StreamService.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import md5 from 'crypto-js/md5';
|
||||
|
||||
export const URL = 'https://developerdays.sailpoint.com';
|
||||
|
||||
|
||||
export async function getFAQ() {
|
||||
try {
|
||||
const response = await fetch(URL + '/faq');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAgenda() {
|
||||
try {
|
||||
const response = await fetch(URL + '/agenda');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSpeaker() {
|
||||
try {
|
||||
const response = await fetch(URL + '/speakers');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRegistration() {
|
||||
try {
|
||||
const response = await fetch(URL + '/registration');
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitSurvey(session, rating, feedback) {
|
||||
if (rating < 1) return false;
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append('Content-Type', 'application/json');
|
||||
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
headers: myHeaders,
|
||||
body: JSON.stringify({
|
||||
session,
|
||||
rating,
|
||||
feedback,
|
||||
}),
|
||||
redirect: 'follow',
|
||||
};
|
||||
|
||||
const response = await fetch(URL + '/survey', requestOptions).catch(
|
||||
(error) => {
|
||||
console.log('error', error);
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
const validated = await response.text();
|
||||
|
||||
if (validated) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function submitAttendance(email, name, title, company) {
|
||||
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email)) {
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append('Content-Type', 'application/json');
|
||||
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
headers: myHeaders,
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
name,
|
||||
title,
|
||||
company,
|
||||
}),
|
||||
redirect: 'follow',
|
||||
};
|
||||
|
||||
const response = await fetch(URL + '/attend', requestOptions).catch(
|
||||
(error) => {
|
||||
console.log('error', error);
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
const validated = await response.text();
|
||||
|
||||
if (validated) {
|
||||
console.log('setting login status');
|
||||
const uuid = md5(email);
|
||||
localStorage.setItem('entry-status', uuid);
|
||||
return uuid;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
15
archive/_devdaysbak.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import Main from '../components/stream/main';
|
||||
|
||||
export default function Stream() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<Layout>
|
||||
<main>
|
||||
<Main></Main>
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
203
archive/agenda/agenda.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function AgendaContent({
|
||||
agenda = {
|
||||
day1: [],
|
||||
day2: [],
|
||||
day3: [],
|
||||
},
|
||||
speakers = [],
|
||||
loading = true,
|
||||
}) {
|
||||
const [filterSelection, setFilterSelection] = React.useState('IDN');
|
||||
|
||||
const dates = {
|
||||
day1: new Date('03/07/2023').toLocaleDateString([], {
|
||||
dateStyle: 'full',
|
||||
}),
|
||||
day2: new Date('03/08/2023').toLocaleDateString([], {
|
||||
dateStyle: 'full',
|
||||
}),
|
||||
day3: new Date('03/09/2023').toLocaleDateString([], {
|
||||
dateStyle: 'full',
|
||||
}),
|
||||
};
|
||||
|
||||
function formatSpeaker(id) {
|
||||
return speakers?.filter((spkr) => spkr?.id === id)[0];
|
||||
}
|
||||
|
||||
function diff_minutes(dt2, dt1) {
|
||||
var diff = (dt2.getTime() - dt1.getTime()) / 1000;
|
||||
diff /= 60;
|
||||
return Math.abs(Math.round(diff));
|
||||
}
|
||||
|
||||
const sessionFilter = (obj) => {
|
||||
if (obj?.hidden === true) return false;
|
||||
return obj?.stage === filterSelection;
|
||||
};
|
||||
|
||||
const iiqSelectedClass =
|
||||
filterSelection === 'IIQ' ? styles.stageButtonActive : '';
|
||||
const idnSelectedClass =
|
||||
filterSelection === 'IDN' ? styles.stageButtonActive : '';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row gap-2 justify-center">
|
||||
<button
|
||||
className={`${
|
||||
styles.stageButton
|
||||
} border-solid rounded border-2 px-2 ${
|
||||
filterSelection === 'IDN'
|
||||
? 'border-[#cc27b0] bg-[#cc27b0] text-white'
|
||||
: 'border-slate-600 bg-transparent'
|
||||
}`}
|
||||
onClick={() => setFilterSelection('IDN')}>
|
||||
IdentityNow
|
||||
</button>
|
||||
<button
|
||||
className={`${
|
||||
styles.stageButton
|
||||
} border-solid rounded border-2 px-2 ${
|
||||
filterSelection === 'IIQ'
|
||||
? 'border-[#cc27b0] bg-[#cc27b0] text-white'
|
||||
: 'border-slate-600 bg-transparent'
|
||||
}`}
|
||||
onClick={() => setFilterSelection('IIQ')}>
|
||||
IdentityIQ
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loading && (
|
||||
<>
|
||||
<div className="flex flex-row justify-center">
|
||||
<p className="text-center">Loading...</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!loading && (
|
||||
<>
|
||||
{['Day 1', 'Day 2', 'Day 3'].map((label) => {
|
||||
const day = label?.replace(' ', '')?.toLowerCase();
|
||||
const sessions = agenda[day]?.filter(sessionFilter);
|
||||
|
||||
if (sessions?.length > 0)
|
||||
return (
|
||||
<div key={day} className="p-2 flex flex-col">
|
||||
<div className="flex flex-row justify-center gap-4">
|
||||
<div className="hidden xl:inline w-[104px]"></div>
|
||||
<p className="xl:!w-[834px] text-xl font-extrabold">
|
||||
{label} - {dates[day]}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{sessions.map((session) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row gap-4 justify-center">
|
||||
<div className="hidden lg:flex flex-col justify-center">
|
||||
<p className="w-[105px] whitespace-nowrap font-bold">
|
||||
{new Date(
|
||||
session?.startTime,
|
||||
).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'short',
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
key={session?.title}
|
||||
className={`${styles.agendaDiv} flex flex-col border-l-8 grow md:grow-0 text-white rounded-lg p-4 md:!w-[834px] transform-gpu transition-all border-[#54c0e8] bg-[#0033a1]`}>
|
||||
<div className="flex flex-col">
|
||||
<div className="lg:hidden">
|
||||
<p className="whitespace-nowrap">
|
||||
{new Date(
|
||||
session?.startTime,
|
||||
).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'short',
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-col grow">
|
||||
<div className="flex flex-row gap-2">
|
||||
<p className="!m-0 text-2xl">
|
||||
{session?.topic}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{session?.speakers?.map((spkr) => {
|
||||
const speaker = formatSpeaker(spkr);
|
||||
return (
|
||||
<div
|
||||
key={spkr}
|
||||
className={`flex flex-row gap-2 ${
|
||||
filterSelection === 'IIQ'
|
||||
? 'text-slate-300/50'
|
||||
: 'text-slate-300/50'
|
||||
}`}>
|
||||
<p className="my-auto text-xl">
|
||||
{speaker?.name}
|
||||
{speaker?.title &&
|
||||
` - ${speaker.title}`}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-center font-medium pr-6 gap-2">
|
||||
{session?.topicLink && (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={session?.topicLink}
|
||||
className="h-8 w-8">
|
||||
<img
|
||||
className="h-8 w-8 bg-transparent hover:animate-pulse"
|
||||
src="/homepage/discourse-icon.png"
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
<div className="hidden lg:inline">
|
||||
<p className="!m-0 text-center">
|
||||
{diff_minutes(
|
||||
new Date(session?.endTime),
|
||||
new Date(session?.startTime),
|
||||
)}
|
||||
</p>
|
||||
<p className="!m-0">min</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:hidden">
|
||||
<p className="!m-0 font-bold">
|
||||
{diff_minutes(
|
||||
new Date(session?.endTime),
|
||||
new Date(session?.startTime),
|
||||
)}{' '}
|
||||
min
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
210
archive/agenda/styles.module.css
Normal file
@@ -0,0 +1,210 @@
|
||||
/* positioning of content */
|
||||
.headerContainer {
|
||||
display: grid;
|
||||
margin-left: 50px;
|
||||
margin-bottom: 50px;
|
||||
grid-gap: 20px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
||||
place-content: center;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.headerContent {
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
/* agenda speakers faq buttons containers */
|
||||
.buttonsContainer {
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
grid-template-columns: 80px 80px 80px;
|
||||
place-content: end;
|
||||
}
|
||||
@media only screen and (max-width: 628px) {
|
||||
.buttonsContainer {
|
||||
place-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonsContent {
|
||||
position: relative;
|
||||
margin-top: 0px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* stage buttons containers */
|
||||
|
||||
.center {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.stageButtonsContainer {
|
||||
/* display: grid;
|
||||
|
||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||
grid-gap: 10px; */
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
@media only screen and (max-width: 628px) {
|
||||
.stageButtonsContainer {
|
||||
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
||||
grid-gap: 10px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.stageButtonsContent {
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* initial header */
|
||||
.headerText {
|
||||
color: var(--dev-text-color-cobalt);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.timeText {
|
||||
margin: 8px 0px;
|
||||
color: var(--dev-text-color-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* agenda speakers and faq buttons */
|
||||
|
||||
.button {
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
/* Layout Properties */
|
||||
width: 80px;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
/* UI Properties */
|
||||
background: transparent 0% 0% no-repeat padding-box;
|
||||
opacity: 1;
|
||||
background-color: #c552ae10;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
/* border: 2px solid #df61ca; */
|
||||
border-radius: 5px;
|
||||
color: #cc27b0;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #c552ae31;
|
||||
color: #cc27b0;
|
||||
}
|
||||
|
||||
/* stage buttons */
|
||||
|
||||
.stageButton {
|
||||
text-decoration: none;
|
||||
/* text-align: center; */
|
||||
/* font-size: 16px; */
|
||||
font-weight: bold;
|
||||
/* line-height: 100%; */
|
||||
/* Layout Properties */
|
||||
width: 130px;
|
||||
/* height: 40px; */
|
||||
line-height: 40px;
|
||||
/* UI Properties */
|
||||
/* background: transparent 0% 0% no-repeat padding-box; */
|
||||
/* background-color: #0071ce; */
|
||||
/* border: 1px solid #dae1e9; */
|
||||
/* box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2); */
|
||||
border-radius: 20.3736px;
|
||||
/* color: #dae1e9; */
|
||||
}
|
||||
|
||||
.stageButton:hover {
|
||||
cursor: pointer;
|
||||
transform: scale(1.03);
|
||||
/* top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #c552ae31;
|
||||
color: #cc27b0; */
|
||||
}
|
||||
|
||||
.stageButtonActive {
|
||||
top: -4px;
|
||||
box-shadow: 0 7px 7px rgba(0, 0, 0, 0.4);
|
||||
background-color: #0033a1 !important;
|
||||
color: #dae1e9 !important;
|
||||
border: 1px solid #dae1e9;
|
||||
}
|
||||
|
||||
.stageButton:disabled {
|
||||
background: #ffffff;
|
||||
box-shadow: inset -5.0934px -5.0934px 15.2802px rgba(255, 255, 255, 0.5),
|
||||
inset 5.0934px 5.0934px 15.2802px rgba(136, 160, 183, 0.25);
|
||||
border-radius: 20.3736px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 628px) {
|
||||
.stageButton {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
margin-top: 50px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 20px;
|
||||
background-color: var(--dev-card-background);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.modalButton {
|
||||
border: 1px solid var(--ifm-color-primary);
|
||||
box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 24px;
|
||||
color: var(--ifm-color-primary);
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
|
||||
background-color: #ffffff31;
|
||||
}
|
||||
|
||||
.modalButton:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #dae1e9;
|
||||
color: #005fc4;
|
||||
}
|
||||
|
||||
.agendaDiv {
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
rgba(0, 51, 161, 1) 40%,
|
||||
rgba(0, 79, 181, 1) 65%,
|
||||
rgba(0, 113, 206, 1) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.agendaDiv:hover {
|
||||
transform: scaleX(1.04) scaleY(1.04);
|
||||
}
|
||||
@@ -12,12 +12,12 @@ export default function ConferenceHero() {
|
||||
<div className={styles.mainCard}>
|
||||
<div>
|
||||
<div className={styles.conferenceText}>Developer Days</div>
|
||||
<div className={styles.comingSoonText}>COMING SOON | Q1 2023</div>
|
||||
<div className={styles.comingSoonText}>March 7th-9th, 2023</div>
|
||||
<div className={styles.descriptionText}>
|
||||
The conference for developers on SailPoint platforms.
|
||||
</div>
|
||||
<SliderButton id="gVCODdMc" className={styles.button}>
|
||||
Join the Waitlist!
|
||||
Register today!
|
||||
</SliderButton>
|
||||
</div>
|
||||
</div>
|
||||
19
archive/conference/Theme/styles.module.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.gridContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-gap: 40px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.center {
|
||||
margin: 20px auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
@@ -4,17 +4,11 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
import {SliderButton} from '@typeform/embed-react';
|
||||
export default function ConferenceWaitlist() {
|
||||
<<<<<<< HEAD
|
||||
const background = "/conf/WaveBackground.png";
|
||||
const learnBuildDeploy = "/conf/LearnBuildDeploy-Light.png";
|
||||
const sailpointImage = "/img/SailPoint-Logo-RGB-Color.png";
|
||||
=======
|
||||
export default function RegisterForConference() {
|
||||
const background = '/conf/Shipyard-WaveBackground.png';
|
||||
const learnBuildDeploy = '/conf/LearnBuildDeploy-Light.png';
|
||||
const shipyardImage = '/conf/SailPoint-Shipyard-DeveloperConference-Logo.png';
|
||||
const sailpointImage = '/img/SailPoint-Logo-RGB-Color.png';
|
||||
>>>>>>> main
|
||||
|
||||
return (
|
||||
<div className={styles.mainCard}>
|
||||
@@ -42,7 +36,7 @@ export default function ConferenceWaitlist() {
|
||||
dark: useBaseUrl(addDarkToFileName(sailpointImage)),
|
||||
}}></ThemedImage>
|
||||
</div>
|
||||
<div className={styles.comingSoon}>Coming Soon | Q1 2023</div>
|
||||
<div className={styles.comingSoon}>March 7th-9th, 2023</div>
|
||||
<div className={styles.welcomeText}>
|
||||
The conference for developers on SailPoint platforms.
|
||||
</div>
|
||||
@@ -51,7 +45,7 @@ export default function ConferenceWaitlist() {
|
||||
</div>
|
||||
<input className={styles.emailInput} type="text" id="fname" name="fname" placeholder="user.name@email.com"></input> */}
|
||||
<SliderButton id="DxKIYwdl" className={styles.button}>
|
||||
Join the Waitlist
|
||||
Register today!
|
||||
</SliderButton>
|
||||
</div>
|
||||
</div>
|
||||
25
archive/faq/faq.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import {getFAQ} from '../../services/StreamService';
|
||||
import Accordion from '../Accordion/Accordion';
|
||||
export default function FAQContent() {
|
||||
const [faqs, setFaqs] = React.useState([]);
|
||||
|
||||
React.useEffect(async () => {
|
||||
let data = await getFAQ();
|
||||
if (!Array.isArray(data)) data = [];
|
||||
setFaqs(data);
|
||||
}, []);
|
||||
|
||||
let itemsList = faqs?.map((item, index) => {
|
||||
return (
|
||||
<Accordion
|
||||
key={item?.question}
|
||||
title={item?.question}
|
||||
content={item?.answer}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return <div className="p-4 gap-2 flex flex-col">{itemsList}</div>;
|
||||
}
|
||||
80
archive/faq/styles.module.css
Normal file
@@ -0,0 +1,80 @@
|
||||
.faq {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.gettingStartedText {
|
||||
text-align: center;
|
||||
margin: 50px auto 50px auto;
|
||||
width: calc(100% - 100px);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.gettingStartedCardIcon {
|
||||
margin-bottom: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.gettingStartedOne {
|
||||
color: var(--ifm-color-primary);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.gettingStartedThree {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.faqContent {
|
||||
height: calc(100vh - 530px);
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.faqQuestion {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.faqAnswer {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
margin-top: 10px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 20px;
|
||||
background-color: var(--dev-card-background);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.modalButton {
|
||||
border: 1px solid var(--ifm-color-primary);
|
||||
box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 24px;
|
||||
color: var(--ifm-color-primary);
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
|
||||
background-color: #ffffff31;
|
||||
}
|
||||
|
||||
.modalButton:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #dae1e9;
|
||||
color: #005fc4;
|
||||
}
|
||||
71
archive/stream/agenda/index.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Modal from 'react-modal';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import {getAgenda} from '../../../services/StreamService';
|
||||
import AgendaContent from '../../agenda/agenda';
|
||||
export default function Agenda({title, image, description, speakers}) {
|
||||
const [agendaModalIsOpen, setAgendaIsOpen] = React.useState(false);
|
||||
const [agenda, setAgenda] = useState({
|
||||
day1: [],
|
||||
day2: [],
|
||||
day3: [],
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(async () => {
|
||||
const tempAgenda = await getAgenda();
|
||||
setAgenda(tempAgenda);
|
||||
console.log(tempAgenda);
|
||||
setLoading(false);
|
||||
console.log('Done Loading');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
className="cursor-pointer border-[color:var(--ifm-color-primary)] md:grow border-2 hover:bg-[color:var(--ifm-color-primary)] hover:text-white text-[color:var(--ifm-color-primary)] border-solid bg-transparent text-center py-2 px-4 rounded"
|
||||
onClick={() => setAgendaIsOpen(true)}>
|
||||
Agenda
|
||||
</button>
|
||||
<Modal
|
||||
isOpen={agendaModalIsOpen}
|
||||
onRequestClose={() => setAgendaIsOpen(false)}
|
||||
className={styles.modal}
|
||||
contentLabel="Agenda">
|
||||
<div className="">
|
||||
<div className={styles.gettingStartedText}>
|
||||
<ThemedImage
|
||||
className={styles.gettingStartedCardIcon}
|
||||
sources={{
|
||||
light: useBaseUrl(image),
|
||||
dark: useBaseUrl(addDarkToFileName(image)),
|
||||
}}></ThemedImage>
|
||||
<div className={styles.gettingStartedOne}>{title}</div>
|
||||
<div
|
||||
className={styles.gettingStartedThree}
|
||||
dangerouslySetInnerHTML={{__html: description}}></div>
|
||||
</div>
|
||||
<div className="md:h-[50vh] sm:w-[90vw] h-[45vh] overflow-auto p-4">
|
||||
<AgendaContent
|
||||
speakers={speakers}
|
||||
agenda={agenda}
|
||||
loading={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end">
|
||||
<button
|
||||
className={styles.modalButton}
|
||||
onClick={() => setAgendaIsOpen(false)}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
93
archive/stream/agenda/styles.module.css
Normal file
@@ -0,0 +1,93 @@
|
||||
.agenda {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.gettingStartedText {
|
||||
text-align: center;
|
||||
margin: 50px auto 50px auto;
|
||||
width: calc(100% - 100px);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.gettingStartedCardIcon {
|
||||
margin-bottom: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.gettingStartedOne {
|
||||
color: var(--ifm-color-primary);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.gettingStartedThree {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.agendaContent {
|
||||
height: 100%;
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.agendaQuestion {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.agendaAnswer {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.dayHeader {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dayContent {
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 20.3736px;
|
||||
padding: 20px;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 20px;
|
||||
background-color: var(--dev-card-background);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.modalButton {
|
||||
border: 1px solid var(--ifm-color-primary);
|
||||
box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 24px;
|
||||
color: var(--ifm-color-primary);
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
|
||||
background-color: #ffffff31;
|
||||
}
|
||||
|
||||
.modalButton:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #dae1e9;
|
||||
color: #005fc4;
|
||||
}
|
||||
80
archive/stream/faq/index.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Modal from 'react-modal';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import {getFAQ} from '../../../services/StreamService';
|
||||
import FAQContent from '../../faq/faq';
|
||||
export default function FAQ({title, image, description}) {
|
||||
const [faqModalIsOpen, setFaqIsOpen] = React.useState(false);
|
||||
const [faqs, setFaqs] = React.useState([]);
|
||||
|
||||
function openFaqModal() {
|
||||
setFaqIsOpen(true);
|
||||
}
|
||||
|
||||
function closeFaqModal() {
|
||||
setFaqIsOpen(false);
|
||||
}
|
||||
|
||||
const getFaqs = async () => {
|
||||
const data = await getFAQ();
|
||||
if (!Array.isArray(data)) data = [];
|
||||
setFaqs(data);
|
||||
};
|
||||
React.useEffect(() => {
|
||||
getFaqs();
|
||||
}, []);
|
||||
|
||||
let itemsList = faqs?.map((item, index) => {
|
||||
return (
|
||||
<div key={`${index}-qa`}>
|
||||
<div className={styles.faqQuestion} key={`${index}-question`}>
|
||||
{item?.question}
|
||||
</div>
|
||||
<div className="px-8 pb-4" key={`${index}-answer`}>
|
||||
{item?.answer}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
className="cursor-pointer border-[color:var(--ifm-color-primary)] md:grow border-2 hover:bg-[color:var(--ifm-color-primary)] hover:text-white text-[color:var(--ifm-color-primary)] border-solid text-center bg-transparent py-2 px-4 rounded"
|
||||
onClick={openFaqModal}>
|
||||
FAQ
|
||||
</button>
|
||||
<Modal
|
||||
isOpen={faqModalIsOpen}
|
||||
onRequestClose={closeFaqModal}
|
||||
className={styles.modal}
|
||||
contentLabel="FAQ">
|
||||
<div className={styles.gettingStartedText}>
|
||||
<ThemedImage
|
||||
className={styles.gettingStartedCardIcon}
|
||||
sources={{
|
||||
light: useBaseUrl(image),
|
||||
dark: useBaseUrl(addDarkToFileName(image)),
|
||||
}}></ThemedImage>
|
||||
<div className={styles.gettingStartedOne}>{title}</div>
|
||||
<div
|
||||
className={styles.gettingStartedThree}
|
||||
dangerouslySetInnerHTML={{__html: description}}></div>
|
||||
</div>
|
||||
<div className="md:h-[50vh] sm:w-[90vw] h-[45vh] overflow-auto p-4">
|
||||
<FAQContent />
|
||||
</div>
|
||||
<div className="flex flex-row justify-end">
|
||||
<button className={styles.modalButton} onClick={closeFaqModal}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
80
archive/stream/faq/styles.module.css
Normal file
@@ -0,0 +1,80 @@
|
||||
.faq {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.gettingStartedText {
|
||||
text-align: center;
|
||||
margin: 50px auto 50px auto;
|
||||
width: calc(100% - 100px);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.gettingStartedCardIcon {
|
||||
margin-bottom: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.gettingStartedOne {
|
||||
color: var(--ifm-color-primary);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.gettingStartedThree {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.faqContent {
|
||||
height: calc(100vh - 530px);
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.faqQuestion {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.faqAnswer {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
margin-top: 10px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 20px;
|
||||
background-color: var(--dev-card-background);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.modalButton {
|
||||
border: 1px solid var(--ifm-color-primary);
|
||||
box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 24px;
|
||||
color: var(--ifm-color-primary);
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
|
||||
background-color: #ffffff31;
|
||||
}
|
||||
|
||||
.modalButton:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #dae1e9;
|
||||
color: #005fc4;
|
||||
}
|
||||
486
archive/stream/main/index.js
Normal file
@@ -0,0 +1,486 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
import Modal from 'react-modal';
|
||||
import Agenda from '../agenda';
|
||||
import FAQ from '../faq';
|
||||
import Room from '../room';
|
||||
import Speakers from '../speakers';
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
import {useEffect, useState} from 'react';
|
||||
import io from 'socket.io-client';
|
||||
import {
|
||||
getRegistration,
|
||||
getSpeaker,
|
||||
submitAttendance,
|
||||
submitSurvey,
|
||||
URL,
|
||||
} from '../../../services/StreamService';
|
||||
|
||||
const socket = io(URL);
|
||||
|
||||
export default function Main() {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
const BackupStageData = {
|
||||
IDN: {
|
||||
day: '1',
|
||||
topic: 'Conference Starting 03.07.23',
|
||||
startTime: '2023-03-07T14:00:00.000Z',
|
||||
endTime: '2023-03-07T15:00:00.000Z',
|
||||
stage: 'IDN',
|
||||
active: true,
|
||||
speakers: [],
|
||||
id: 'reciq5lGqHOyl1Zzy',
|
||||
muxPlaybackId: 'placeholdervideo',
|
||||
muxEnvironmentKey: 'j4iije0sv1ih8shgurfp3ldkq',
|
||||
typeformId: 'RlYAvjZo',
|
||||
surveyDisplayed: false,
|
||||
allStages: false,
|
||||
hidden: true,
|
||||
},
|
||||
IIQ: {
|
||||
day: '1',
|
||||
topic: 'Conference Starting 03.07.23',
|
||||
startTime: '2023-03-07T14:00:00.000Z',
|
||||
endTime: '2023-03-07T15:00:00.000Z',
|
||||
stage: 'IIQ',
|
||||
active: true,
|
||||
speakers: [],
|
||||
id: 'reciq5lGqHOyl1Zzy',
|
||||
muxPlaybackId: 'placeholdervideo',
|
||||
muxEnvironmentKey: 'j4iije0sv1ih8shgurfp3ldkq',
|
||||
typeformId: 'RlYAvjZo',
|
||||
surveyDisplayed: false,
|
||||
allStages: false,
|
||||
hidden: true,
|
||||
},
|
||||
};
|
||||
|
||||
const [stages, setStages] = useState(BackupStageData);
|
||||
const [stage, setStage] = useState('IDN');
|
||||
const [speakers, setSpeakers] = useState([]);
|
||||
const [surveyOpen, setSurveyOpen] = useState(false);
|
||||
const [loginOpen, setLoginOpen] = useState(false);
|
||||
|
||||
const [rating, setRating] = useState(0);
|
||||
const [hover, setHover] = useState(0);
|
||||
const [feedback, setFeedback] = useState('');
|
||||
|
||||
const [emailValidationError, setEmailValidationError] = useState(false);
|
||||
const [starValidationError, setStarValidationError] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
const [title, setTitle] = useState('');
|
||||
const [company, setCompany] = useState('');
|
||||
const [userID, setUserID] = useState('Unregistered User');
|
||||
|
||||
const getSpeakers = async () => {
|
||||
const data = await getSpeaker();
|
||||
console.log('Speaker Data');
|
||||
console.log(data);
|
||||
setSpeakers(data);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
getSpeakers();
|
||||
}, []);
|
||||
|
||||
React.useEffect(async () => {
|
||||
console.log('Starting Registration');
|
||||
const reg = await getRegistration();
|
||||
console.log(reg);
|
||||
openLoginPage(reg);
|
||||
}, []);
|
||||
|
||||
//setting socket here
|
||||
useEffect(() => {
|
||||
console.log('Creating effect');
|
||||
socket.on('connect', () => {
|
||||
console.log('Socket Connect');
|
||||
socket.emit('register');
|
||||
setIsConnected(true);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
setIsConnected(false);
|
||||
});
|
||||
|
||||
socket.on('stream', (data) => {
|
||||
console.log('incoming Data');
|
||||
console.log(data);
|
||||
|
||||
if (data?.stages?.IDN) {
|
||||
if (Object.keys(data.stages?.IDN).length <= 0) {
|
||||
data.stages.IDN = BackupStageData.IDN;
|
||||
}
|
||||
}
|
||||
|
||||
if (data?.stages?.IIQ) {
|
||||
if (Object.keys(data.stages?.IIQ).length <= 0) {
|
||||
data.stages.IIQ = BackupStageData.IIQ;
|
||||
}
|
||||
}
|
||||
|
||||
setStages(data.stages);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.off('connect');
|
||||
socket.off('disconnect');
|
||||
socket.off('stream');
|
||||
socket.off('survey');
|
||||
};
|
||||
}, []);
|
||||
|
||||
function openSurvey() {
|
||||
setFeedback('');
|
||||
setRating(0);
|
||||
setHover(0);
|
||||
setStarValidationError(false);
|
||||
setSurveyOpen(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
socket.on('survey', (data) => {
|
||||
if (stage === data) {
|
||||
openSurvey();
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
socket.off('survey');
|
||||
};
|
||||
}, [stage]);
|
||||
|
||||
function changeToIDNStage() {
|
||||
setStage('IDN');
|
||||
console.log('Changing Stage');
|
||||
}
|
||||
function changeToIIQStage() {
|
||||
setStage('IIQ');
|
||||
console.log('Changing Stage');
|
||||
}
|
||||
|
||||
function openLoginPage(reg) {
|
||||
setTimeout(() => {
|
||||
console.log('opening login page');
|
||||
let pop_status = localStorage.getItem('entry-status');
|
||||
console.log(pop_status);
|
||||
console.log(reg);
|
||||
setUserID(pop_status);
|
||||
if (!pop_status && reg) {
|
||||
console.log('open');
|
||||
setLoginOpen(true);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
Modal.setAppElement('#__docusaurus');
|
||||
|
||||
const eventSpeakers = stages[stage]?.speakers?.map((speakerId, index) => {
|
||||
return speakers.filter((obj) => obj.id === speakerId)[0];
|
||||
});
|
||||
|
||||
const iiqSelectedClass = stage === 'IIQ' ? styles.stageButtonActive : '';
|
||||
const idnSelectedClass = stage === 'IDN' ? styles.stageButtonActive : '';
|
||||
|
||||
return (
|
||||
<div className={styles.main}>
|
||||
<div className="px-2 md:px-4 py-2 flex flex-col lg:flex-row justify-between gap-4">
|
||||
<div className="">
|
||||
<div className={`${styles.headerText} my-auto`}>
|
||||
{stages[stage]?.topic}
|
||||
</div>
|
||||
|
||||
<div className={styles.timeText}>
|
||||
{stages[stage]?.startTime &&
|
||||
new Date(stages[stage]?.startTime).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
{(stages[stage]?.endTime &&
|
||||
`-` +
|
||||
new Date(stages[stage]?.endTime).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})) ||
|
||||
null}
|
||||
</div>
|
||||
{stages[stage]?.topicLink && (
|
||||
<div className="py-2">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={stages[stage]?.topicLink}>
|
||||
Discuss this topic in the Developer Community
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row flex-wrap gap-8 py-2">
|
||||
{eventSpeakers?.map((spkr) => {
|
||||
return (
|
||||
<div key={spkr?.name} className="flex flex-row gap-2">
|
||||
<img src={spkr?.image} className="rounded-full w-12 h-12" />
|
||||
<div className="flex flex-col justify-center">
|
||||
<div className={`${styles.speakerText} font-bold text-lg`}>
|
||||
{spkr?.name}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.speakerText} font-semibold text-base whitespace-nowrap`}>
|
||||
{spkr?.title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row md:justify-start justify-center gap-2 py-2">
|
||||
<button
|
||||
className={`${styles.stageButton} ${idnSelectedClass} border-solid px-4 min-w-[140px]`}
|
||||
onClick={changeToIDNStage}>
|
||||
<p className="text-lg whitespace-nowrap my-0">IdentityNow</p>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`${styles.stageButton} ${iiqSelectedClass} border-solid px-4 min-w-[140px]`}
|
||||
onClick={changeToIIQStage}>
|
||||
<p className="text-lg text-center whitespace-nowrap my-0">
|
||||
IdentityIQ
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-row gap-1 md:gap-2 w-full justify-center lg:justify-between">
|
||||
<Agenda
|
||||
description={
|
||||
'The agenda for the 3 conference days are below. If you have any questions about the agenda, reach out via the discussion forum.'
|
||||
}
|
||||
title={'Agenda'}
|
||||
image={'/homepage/team.png'}
|
||||
speakers={speakers}
|
||||
/>
|
||||
<FAQ
|
||||
description={
|
||||
"if you still can't find what you are looking for, reach out to us on our discussion board"
|
||||
}
|
||||
title={'Frequently Asked Questions'}
|
||||
image={'/homepage/discuss.png'}
|
||||
/>
|
||||
<Speakers
|
||||
description={
|
||||
'Here are the awesome speakers we have lined up for Developer Days 2023'
|
||||
}
|
||||
title={'Speakers'}
|
||||
image={'/homepage/person-head.png'}
|
||||
speakers={speakers}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="cursor-pointer border-[color:var(--ifm-color-primary)] md:grow border-2 hover:bg-[color:var(--ifm-color-primary)] hover:text-white text-[color:var(--ifm-color-primary)] border-solid text-center bg-transparent py-2 px-4 rounded"
|
||||
onClick={() => openSurvey()}>
|
||||
Survey
|
||||
</button>
|
||||
<Modal
|
||||
isOpen={surveyOpen}
|
||||
onRequestClose={() => setSurveyOpen(false)}
|
||||
className={styles.modal}
|
||||
contentLabel="Survey">
|
||||
<div className="h-[60vh] w-full overflow-auto p-4 gap-2 flex flex-col">
|
||||
<div className="flex flex-row justify-end">
|
||||
<button
|
||||
onClick={() => setSurveyOpen(false)}
|
||||
className="bg-transparent border-none">
|
||||
<p className="text-2xl my-auto">X</p>
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.gettingStartedText}>
|
||||
<div className={styles.gettingStartedOne}>Survey</div>
|
||||
<div
|
||||
className={styles.gettingStartedThree}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
'We want to hear from you, Let us know what you think!',
|
||||
}}></div>
|
||||
</div>
|
||||
<ol className="flex flex-col grow justify-center gap-12 m-0 px-8">
|
||||
<li>
|
||||
<p className="my-0">
|
||||
How valuable was the session "{stages[stage]?.topic}" to
|
||||
you?
|
||||
{starValidationError === true && (
|
||||
<p class="text-red-500 my-0 pl-2">
|
||||
Rating is required
|
||||
</p>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className="py-4">
|
||||
{[...Array(5)].map((star, index) => {
|
||||
index += 1;
|
||||
return (
|
||||
<button
|
||||
key={index}
|
||||
className={`cursor-pointer bg-transparent border-none outline-none ${
|
||||
index <= (hover || rating)
|
||||
? 'text-yellow-400'
|
||||
: 'text-gray-200'
|
||||
}
|
||||
`}
|
||||
onClick={() => setRating(index)}
|
||||
onMouseEnter={() => setHover(index)}
|
||||
onMouseLeave={() => setHover(rating)}>
|
||||
<span className="text-3xl">★</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
Is there anything else you'd like the presenter(s) of
|
||||
this session to know?
|
||||
</p>
|
||||
<textarea
|
||||
className="max-w-full w-full h-40 resize-none block p-2.5 font-[poppins] rounded-lg border focus:ring-blue-500 focus:border-blue-500 placeholder:text-[color:var(--ifm-color-primary)]"
|
||||
placeholder="Write your thoughts here..."
|
||||
onInput={(e) => {
|
||||
setFeedback(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end">
|
||||
<button
|
||||
className={styles.modalButton}
|
||||
onClick={async () => {
|
||||
const validated = await submitSurvey(
|
||||
stages[stage].id,
|
||||
rating,
|
||||
feedback,
|
||||
);
|
||||
if (validated != false) {
|
||||
setSurveyOpen(false);
|
||||
} else {
|
||||
setStarValidationError(true);
|
||||
}
|
||||
}}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BrowserOnly>
|
||||
{() => <Room userID={userID} videoSource={stages[stage]}></Room>}
|
||||
</BrowserOnly>
|
||||
<Modal
|
||||
isOpen={loginOpen}
|
||||
onRequestClose={openLoginPage}
|
||||
className={styles.modal}
|
||||
contentLabel="Survey">
|
||||
<div className="md:h-[70vh] overflow-auto p-4">
|
||||
<div className="h-full flex flex-row justify-center w-full">
|
||||
<ul className="flex flex-col justify-center gap-6 m-0 px-8 list-none">
|
||||
<li>
|
||||
<p>Tell us a little bit about yourself...</p>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
What is your email address?
|
||||
{emailValidationError === true && (
|
||||
<p class="text-red-500 my-0 pl-2">Error Validating Email</p>
|
||||
)}
|
||||
<input
|
||||
className="max-w-full md:w-[420px] w-[200px] resize-none block p-2.5 font-[poppins] rounded-lg border focus:ring-blue-500 focus:border-blue-500 placeholder:text-[color:var(--ifm-color-primary)]"
|
||||
placeholder="Email"
|
||||
onInput={(e) => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
What name shall we address you by?
|
||||
<input
|
||||
className="max-w-full md:w-[420px] w-[200px] resize-none block p-2.5 font-[poppins] rounded-lg border focus:ring-blue-500 focus:border-blue-500 placeholder:text-[color:var(--ifm-color-primary)]"
|
||||
placeholder="Name"
|
||||
onInput={(e) => {
|
||||
setName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
What title are you most often addressed by?
|
||||
<select
|
||||
className="max-w-full md:w-[420px] w-[200px] resize-none block p-2.5 font-[poppins] rounded-lg border focus:ring-blue-500 focus:border-blue-500 placeholder:text-[color:var(--ifm-color-primary)]"
|
||||
placeholder="Title"
|
||||
onChange={(e) => {
|
||||
setTitle(e.target.value);
|
||||
}}
|
||||
defaultValue="select">
|
||||
<option disabled>select</option>
|
||||
<option>Developer</option>
|
||||
<option>IAM/Security Engineer</option>
|
||||
<option>Architect</option>
|
||||
<option>Director</option>
|
||||
<option>SVP/VP</option>
|
||||
<option>CxO</option>
|
||||
<option>Other</option>
|
||||
</select>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
What company are you joining on behalf of today?
|
||||
<input
|
||||
className="max-w-full md:w-[420px] w-[200px] resize-none block p-2.5 font-[poppins] rounded-lg border focus:ring-blue-500 focus:border-blue-500 placeholder:text-[color:var(--ifm-color-primary)]"
|
||||
placeholder="Company"
|
||||
onInput={(e) => {
|
||||
setCompany(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end">
|
||||
<button
|
||||
className={styles.modalButton}
|
||||
onClick={async () => {
|
||||
const validated = await submitAttendance(
|
||||
email.toLowerCase(),
|
||||
name,
|
||||
title,
|
||||
company,
|
||||
);
|
||||
if (validated != false) {
|
||||
setUserID(validated);
|
||||
setLoginOpen(false);
|
||||
} else {
|
||||
setEmailValidationError(true);
|
||||
}
|
||||
}}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
222
archive/stream/main/styles.module.css
Normal file
@@ -0,0 +1,222 @@
|
||||
/* positioning of content */
|
||||
.headerContainer {
|
||||
display: grid;
|
||||
margin-left: 50px;
|
||||
margin-bottom: 50px;
|
||||
grid-gap: 20px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
|
||||
place-content: center;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.headerContent {
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
/* agenda speakers faq buttons containers */
|
||||
.buttonsContainer {
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
grid-template-columns: 80px 80px 80px;
|
||||
place-content: end;
|
||||
}
|
||||
@media only screen and (max-width: 628px) {
|
||||
.buttonsContainer {
|
||||
place-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonsContent {
|
||||
position: relative;
|
||||
margin-top: 0px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* stage buttons containers */
|
||||
|
||||
.center {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.stageButtonsContainer {
|
||||
/* display: grid;
|
||||
|
||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||
grid-gap: 10px; */
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
@media only screen and (max-width: 628px) {
|
||||
.stageButtonsContainer {
|
||||
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
|
||||
grid-gap: 10px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.stageButtonsContent {
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* initial header */
|
||||
.headerText {
|
||||
color: var(--dev-text-color-cobalt);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.timeText {
|
||||
margin: 8px 0px;
|
||||
color: var(--dev-text-color-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* agenda speakers and faq buttons */
|
||||
|
||||
.button {
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
/* Layout Properties */
|
||||
width: 80px;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
/* UI Properties */
|
||||
background: transparent 0% 0% no-repeat padding-box;
|
||||
opacity: 1;
|
||||
background-color: #c552ae10;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
/* border: 2px solid #df61ca; */
|
||||
border-radius: 5px;
|
||||
color: #cc27b0;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #c552ae31;
|
||||
color: #cc27b0;
|
||||
}
|
||||
|
||||
/* stage buttons */
|
||||
|
||||
.stageButton {
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
line-height: 100%;
|
||||
/* Layout Properties */
|
||||
width: 130px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
/* UI Properties */
|
||||
background: transparent 0% 0% no-repeat padding-box;
|
||||
background-color: #0071ce;
|
||||
/* border: 1px solid #dae1e9; */
|
||||
/* box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2); */
|
||||
border-radius: 20.3736px;
|
||||
color: #dae1e9;
|
||||
}
|
||||
|
||||
.stageButton:hover {
|
||||
cursor: pointer;
|
||||
transform: scale(1.03);
|
||||
/* top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #c552ae31;
|
||||
color: #cc27b0; */
|
||||
}
|
||||
|
||||
.stageButtonActive {
|
||||
top: -4px;
|
||||
box-shadow: 0 7px 7px rgba(0, 0, 0, 0.4);
|
||||
background-color: #0033a1 !important;
|
||||
color: #dae1e9 !important;
|
||||
border: 1px solid #dae1e9;
|
||||
}
|
||||
|
||||
.stageButton:disabled {
|
||||
background: #ffffff;
|
||||
box-shadow: inset -5.0934px -5.0934px 15.2802px rgba(255, 255, 255, 0.5),
|
||||
inset 5.0934px 5.0934px 15.2802px rgba(136, 160, 183, 0.25);
|
||||
border-radius: 20.3736px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 628px) {
|
||||
.stageButton {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
margin-top: 50px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 20px;
|
||||
background-color: var(--dev-card-background);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.modalButton {
|
||||
border: 1px solid var(--ifm-color-primary);
|
||||
box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 24px;
|
||||
color: var(--ifm-color-primary);
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
|
||||
background-color: #ffffff31;
|
||||
}
|
||||
|
||||
.modalButton:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #dae1e9;
|
||||
color: #005fc4;
|
||||
}
|
||||
|
||||
.gettingStartedText {
|
||||
text-align: center;
|
||||
margin: 50px auto 50px auto;
|
||||
width: calc(100% - 100px);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.gettingStartedCardIcon {
|
||||
margin-bottom: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.gettingStartedOne {
|
||||
color: var(--ifm-color-primary);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.gettingStartedThree {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
56
archive/stream/room/index.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import MuxPlayer from '@mux/mux-player-react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import WidgetBot from '@widgetbot/react-embed';
|
||||
|
||||
export default function Room({videoSource, userID}) {
|
||||
const [test, setTest] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
setTest(test);
|
||||
}, [videoSource?.topic]);
|
||||
|
||||
console.log(userID);
|
||||
|
||||
const staticVideo = '/img/developer_days_placeholder_video.mov';
|
||||
const placeholderVideo = 'placeholdervideo';
|
||||
|
||||
return (
|
||||
<div className="flex lg:flex-row flex-col gap-[.3%] h-full p-[.3%]">
|
||||
<div className="lg:w-[60%] w-full overflow-hidden rounded-lg">
|
||||
<MuxPlayer
|
||||
src={
|
||||
videoSource?.muxPlaybackId === placeholderVideo ? staticVideo : null
|
||||
}
|
||||
loop={true}
|
||||
autoPlay={true}
|
||||
streamType={
|
||||
videoSource?.muxPlaybackId === placeholderVideo ? null : 'live'
|
||||
}
|
||||
playbackId={
|
||||
videoSource?.muxPlaybackId === placeholderVideo
|
||||
? ''
|
||||
: videoSource?.muxPlaybackId
|
||||
}
|
||||
envKey={videoSource?.muxEnvironmentKey}
|
||||
metadata={{
|
||||
player_name: 'SailPoint Developer Community - Developer Days',
|
||||
video_id: videoSource?.id,
|
||||
video_title: `${videoSource?.id} - ${videoSource?.topic}`,
|
||||
viewer_user_id: userID,
|
||||
autoPlay: true,
|
||||
}}
|
||||
className="aspect-video h-full"
|
||||
/>
|
||||
</div>
|
||||
<WidgetBot
|
||||
className="w-full lg:w-[40%] grow lg:h-auto h-[720px]"
|
||||
server="1039765757011165194"
|
||||
channel="1039765757556428882"
|
||||
shard="https://e-sp.widgetbot.co"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
archive/stream/room/styles.module.css
Normal file
@@ -0,0 +1,44 @@
|
||||
.stageContentVideo {
|
||||
flex-grow: 1;
|
||||
aspect-ratio: 16/9;
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
/* Actual stage containers */
|
||||
.stageContainer {
|
||||
/* margin-top: 1em; */
|
||||
padding: 0.5%;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stageContentChat {
|
||||
min-height: 720px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.stageContentChat > iframe {
|
||||
min-height: 720px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
/* Actual stage containers */
|
||||
.stageContainer {
|
||||
padding: 0.5%;
|
||||
gap: 0.5%;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.stageContentChat {
|
||||
min-width: 30%;
|
||||
}
|
||||
}
|
||||
91
archive/stream/speakers/index.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Modal from 'react-modal';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
export default function Speakers({title, image, description, speakers}) {
|
||||
const [speakersModalIsOpen, setSpeakerIsOpen] = React.useState(false);
|
||||
|
||||
function openSpeakersModal() {
|
||||
setSpeakerIsOpen(true);
|
||||
}
|
||||
|
||||
function closeSpeakersModal() {
|
||||
setSpeakerIsOpen(false);
|
||||
}
|
||||
|
||||
const speakerFilter = (obj) => {
|
||||
if (obj.name === 'Speaker Coming Soon') return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
let itemsList = speakers.filter(speakerFilter).map((item, index) => {
|
||||
return (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={item?.link}
|
||||
key={`${index}-link`}>
|
||||
<div className="!h-fit p-4 flex flex-row gap-2">
|
||||
{item?.image && (
|
||||
<img
|
||||
className="!h-16 !w-16 rounded-full"
|
||||
src={item?.image}
|
||||
key={`${index}-image`}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col justify-center">
|
||||
<p className="text-xl font-bold !m-0" key={`${index}-name`}>
|
||||
{item?.name}
|
||||
</p>
|
||||
<p className="!m-0" key={`${index}-title`}>
|
||||
{item?.title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
className="cursor-pointer border-[color:var(--ifm-color-primary)] md:grow border-2 hover:bg-[color:var(--ifm-color-primary)] hover:text-white text-[color:var(--ifm-color-primary)] border-solid text-center bg-transparent py-2 px-4 rounded"
|
||||
onClick={openSpeakersModal}>
|
||||
Speakers
|
||||
</button>
|
||||
<Modal
|
||||
isOpen={speakersModalIsOpen}
|
||||
onRequestClose={closeSpeakersModal}
|
||||
className={styles.modal}
|
||||
contentLabel="Speakers">
|
||||
<div className="">
|
||||
<div className={styles.gettingStartedText}>
|
||||
<ThemedImage
|
||||
className={styles.gettingStartedCardIcon}
|
||||
sources={{
|
||||
light: useBaseUrl(image),
|
||||
dark: useBaseUrl(addDarkToFileName(image)),
|
||||
}}></ThemedImage>
|
||||
<div className={styles.gettingStartedOne}>{title}</div>
|
||||
<div
|
||||
className={styles.gettingStartedThree}
|
||||
dangerouslySetInnerHTML={{__html: description}}></div>
|
||||
</div>
|
||||
|
||||
<ul className="md:h-[50vh] w-full h-[40vh] overflow-auto p-4 gap-2 flex flex-col">
|
||||
{itemsList}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end">
|
||||
<button className={styles.modalButton} onClick={closeSpeakersModal}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
80
archive/stream/speakers/styles.module.css
Normal file
@@ -0,0 +1,80 @@
|
||||
.faq {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.gettingStartedText {
|
||||
text-align: center;
|
||||
margin: 50px auto 50px auto;
|
||||
width: calc(100% - 100px);
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.gettingStartedCardIcon {
|
||||
margin-bottom: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.gettingStartedOne {
|
||||
color: var(--ifm-color-primary);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.gettingStartedThree {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.speakerContent {
|
||||
height: calc(100vh - 430px);
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.speakerTitle {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.speakerName {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
margin-top: 10px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 2px 3px 10px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 20px;
|
||||
background-color: var(--dev-card-background);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.modalButton {
|
||||
border: 1px solid var(--ifm-color-primary);
|
||||
box-shadow: 1px 4px 10px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 24px;
|
||||
color: var(--ifm-color-primary);
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 120px;
|
||||
|
||||
background-color: #ffffff31;
|
||||
}
|
||||
|
||||
.modalButton:hover {
|
||||
cursor: pointer;
|
||||
top: -4px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #dae1e9;
|
||||
color: #005fc4;
|
||||
}
|
||||
10
archive/stream/video/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import styles from "./styles.module.css";
|
||||
import Link from '@docusaurus/Link';
|
||||
export default function Video() {
|
||||
return (
|
||||
<div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
0
archive/stream/video/styles.module.css
Normal file
@@ -16,6 +16,9 @@ module.exports = {
|
||||
{to: '/idn/api/beta', label: 'Beta APIs', className: 'indent'},
|
||||
{to: '#', label: 'Documentation', className: 'navbar__section'},
|
||||
{to: 'idn/docs', label: 'IDN Documentation', className: 'indent'},
|
||||
{to: '#', label: 'Tools', className: 'navbar__section'},
|
||||
{to: 'idn/tools/cli', label: 'CLI', className: 'indent'},
|
||||
{to: 'idn/tools/sdk', label: 'SDKs', className: 'indent'},
|
||||
{to: '#', label: 'External Links', className: 'navbar__section'},
|
||||
{
|
||||
href: 'https://documentation.sailpoint.com',
|
||||
@@ -62,7 +65,7 @@ module.exports = {
|
||||
{
|
||||
position: 'left',
|
||||
label: 'Discuss',
|
||||
to: 'https://developer.sailpoint.com/discuss',
|
||||
to: 'https://developer.sailpoint.com/discuss/',
|
||||
},
|
||||
{
|
||||
type: 'dropdown',
|
||||
|
||||
3476
package-lock.json
generated
17
package.json
@@ -21,12 +21,15 @@
|
||||
"rebuild-docs": "npm run clean-api-docs-all && npm run gen-api-docs-all"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/theme-mermaid": "^2.2.0",
|
||||
"@docusaurus/plugin-client-redirects": "2.2.0",
|
||||
"@docusaurus/theme-mermaid": "2.2.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"@typeform/embed-react": "^1.21.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"classnames": "^2.3.2",
|
||||
"clsx": "^1.1.1",
|
||||
"docusaurus-plugin-openapi-docs": "^1.4.5",
|
||||
"docusaurus-theme-openapi-docs": "^1.4.5",
|
||||
"docusaurus-plugin-openapi-docs": "^1.5.1",
|
||||
"docusaurus-theme-openapi-docs": "^1.5.1",
|
||||
"prism-react-renderer": "^1.3.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
@@ -35,10 +38,10 @@
|
||||
"mermaid": "9.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/core": "^2.2.0",
|
||||
"@docusaurus/module-type-aliases": "^2.2.0",
|
||||
"@docusaurus/plugin-google-gtag": "^2.2.0",
|
||||
"@docusaurus/preset-classic": "^2.2.0",
|
||||
"@docusaurus/core": "2.2.0",
|
||||
"@docusaurus/module-type-aliases": "2.2.0",
|
||||
"@docusaurus/plugin-google-gtag": "2.2.0",
|
||||
"@docusaurus/preset-classic": "2.2.0",
|
||||
"husky": "^8.0.2",
|
||||
"prettier": "2.8.0",
|
||||
"pretty-quick": "^3.1.3"
|
||||
|
||||
16
plugins.js
@@ -1,4 +1,15 @@
|
||||
module.exports = [
|
||||
[
|
||||
'@docusaurus/plugin-client-redirects',
|
||||
{
|
||||
redirects: [
|
||||
{
|
||||
to: '/',
|
||||
from: ['/conf', '/developerdays', '/developerdays/agenda'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
'@docusaurus/plugin-content-docs',
|
||||
{
|
||||
@@ -43,6 +54,7 @@ module.exports = [
|
||||
idn_v3: {
|
||||
specPath: 'static/api-specs/idn/sailpoint-api.v3.yaml',
|
||||
outputDir: 'products/idn/api/v3',
|
||||
downloadUrl: 'https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.v3.yaml',
|
||||
sidebarOptions: {
|
||||
groupPathsBy: 'tag',
|
||||
categoryLinkSource: 'tag',
|
||||
@@ -52,6 +64,7 @@ module.exports = [
|
||||
idn_beta: {
|
||||
specPath: 'static/api-specs/idn/sailpoint-api.beta.yaml',
|
||||
outputDir: 'products/idn/api/beta',
|
||||
downloadUrl: 'https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/dereferenced/deref-sailpoint-api.beta.yaml',
|
||||
sidebarOptions: {
|
||||
groupPathsBy: 'tag',
|
||||
categoryLinkSource: 'tag',
|
||||
@@ -68,8 +81,9 @@ module.exports = [
|
||||
docsPluginId: 'iiq',
|
||||
config: {
|
||||
iiq: {
|
||||
specPath: 'static/api-specs/iiq/swagger.json',
|
||||
specPath: 'static/api-specs/iiq/sailpoint-api.iiq.yaml',
|
||||
outputDir: 'products/iiq/api',
|
||||
downloadUrl: 'https://raw.githubusercontent.com/sailpoint-oss/api-specs/main/iiq/sailpoint-api.iiq.yaml',
|
||||
sidebarOptions: {
|
||||
groupPathsBy: 'tag',
|
||||
categoryLinkSource: 'tag',
|
||||
|
||||
@@ -283,9 +283,9 @@ curl -X POST \
|
||||
|
||||
### Client Credentials Grant Flow
|
||||
|
||||
Further Reading: [https://oauth.net/2/grant-types/client-credentials/](https://oauth.net/2/grant-types/client-credentials/)
|
||||
Clients use the 'Client Credentials' grant type to obtain access tokens without user context. This is probably the simplest authentication flow, but it has a drawback: API endpoints that require [user level permissions](https://documentation.sailpoint.com/saas/help/common/users/user_level_matrix.html) will not work with them.
|
||||
|
||||
This grant type is used by clients to obtain an access token outside the context of a user. This is probably the simplest authentication flow, but comes with a major drawback; API endpoints that require [user level permissions](https://documentation.sailpoint.com/saas/help/common/users/user_level_matrix.html) will not work. [Personal Access Tokens](#personal-access-tokens) are a form of Client Credentials that have a user context, so they do not share this drawback. However, the APIs that can be invoked with a personal access token depend on the permissions of the user that generated it.
|
||||
[Personal access tokens](#personal-access-tokens) are client credentials that have user context, so the API endpoints requiring user level permissions do work with them. The endpoints a personal access token (PAT) can be used to invoke depend on the permissions of the user who generated it.
|
||||
|
||||
An OAuth 2.0 client using the Client Credentials flow must have `CLIENT_CREDENTIALS` as one of its grantTypes:
|
||||
|
||||
@@ -304,34 +304,53 @@ An OAuth 2.0 client using the Client Credentials flow must have `CLIENT_CREDENTI
|
||||
}
|
||||
```
|
||||
|
||||
[Personal Access Tokens](#personal-access-tokens) are implicly granted a `CLIENT_CREDENTIALS` grant type.
|
||||
PATs are implicitly granted the `CLIENT_CREDENTIALS` grant type.
|
||||
|
||||
The overall authorization flow looks like this:
|
||||
|
||||
1. The client submits an **OAuth 2.0 Token Request** to IdentityNow in the form:
|
||||
This is the overall authorization flow:
|
||||
|
||||
1. The client first submits an OAuth 2.0 token request to IDN in this form:
|
||||
|
||||
```text
|
||||
POST https://{tenant}.api.identitynow.com/oauth/token?grant_type=client_credentials&client_id={client-id}&client_secret={client-secret}
|
||||
POST https://{tenant}.api.identitynow.com/oauth/token
|
||||
```
|
||||
|
||||
The request includes the client credential information passed in the request body, as shown in this example using [Postman](https://www.getpostman.com):
|
||||
|
||||
2. IdentityNow validates the token request and submits a response. If successful, the response will contain a JWT access token.
|
||||

|
||||
|
||||
The query parameters in the OAuth 2.0 Token Request for the Client Credentials grant are as follows:
|
||||
This example shows how to pass the information with form-data in the request body. You can also use these options to pass in the information:
|
||||
|
||||
- Use x-www-form-urlencoded data to pass in the client credential information in the request body.
|
||||
- Use query parameters to pass the information in the request URL. The request URL will look like this:
|
||||
```text
|
||||
https://{tenant}.api.identitynow.com/oauth/token?grant_type=client_credentials&client_id={{clientId}}&client_secret={{clientSecret}}
|
||||
```
|
||||
- If you are using Postman, you can use the 'Authorization' tab to pass in the client credentials. If you use this option, you must also specify the access token URL: https://{tenant}.api.identitynow.com/oauth/token
|
||||
|
||||
The OAuth 2.0 token request must include this information:
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| `grant_type` | Set to `CLIENT_CREDENTIALS` for the authorization code grant type. |
|
||||
| `client_id` | This is the client ID describing for the API client (e.g. `b61429f5-203d-494c-94c3-04f54e17bc5c`). This can be generated at `https://{tenant}.identitynow.com/ui/admin/#admin:global:security:apimanagementpanel` or by [creating a personal access token](#personal-access-tokens). |
|
||||
| `client_secret` | This is the client secret describing for the API client (e.g. `c924417c85b19eda40e171935503d8e9747ca60ddb9b48ba4c6bb5a7145fb6c5`). This can be generated at `https://{tenant}.identitynow.com/ui/admin/#admin:global:security:apimanagementpanel` or by [creating a personal access token](#personal-access-tokens). |
|
||||
| `grant_type` | This is set to `CLIENT_CREDENTIALS` for the authorization code grant type. |
|
||||
| `client_id` | This is the API client's ID (e.g. `b61429f5-203d-494c-94c3-04f54e17bc5c`). You can generate this ID at `https://{tenant}.identitynow.com/ui/admin/#admin:global:security:apimanagementpanel`, or you can generate it when you create a PAT. |
|
||||
| `client_secret` | This is the API client's secret describing (e.g. `c924417c85b19eda40e171935503d8e9747ca60ddb9b48ba4c6bb5a7145fb6c5`). You can generate this secret at `https://{tenant}.identitynow.com/ui/admin/#admin:global:security:apimanagementpanel`, or you can generate it when you create a PAT. |
|
||||
|
||||
Here is an example request to generate an `access_token` using Client Credentials.
|
||||
This example cURL command passes client credentials in the body as form-data to generate an access token:
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
'https://{tenant}.api.identitynow.com/oauth/token?grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}' \
|
||||
-H 'cache-control: no-cache'
|
||||
curl --location 'https://{tenant}.api.identitynow.com/oauth/token' \
|
||||
--header 'scope: sp:scope:all' \
|
||||
--form 'grant_type="client_credentials"' \
|
||||
--form 'client_id="{clientId}"' \
|
||||
--form 'client_secret="{clientSecret}"'
|
||||
```
|
||||
|
||||
2. IdentityNow validates the token request and responds. A successful response will contain a JWT access token.
|
||||
|
||||
Once you have the JWT access token, you can pass the token as a basic "Authorization" header in your requests using the OAuth endpoints.
|
||||
|
||||
To learn more about the OAuth client credentials grant flow, refer [here](https://oauth.net/2/grant-types/client-credentials/).
|
||||
|
||||
### Refresh Token Grant Flow
|
||||
|
||||
Further Reading: [https://oauth.net/2/grant-types/refresh-token/](https://oauth.net/2/grant-types/refresh-token/)
|
||||
|
||||
|
After Width: | Height: | Size: 38 KiB |
6
products/idn/api/img/button.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="32" fill="none">
|
||||
<rect width="128" height="32" rx="4" fill="#FF6C37"/>
|
||||
<path d="M12 10.883a.5.5 0 0 1 .757-.429l8.528 5.117a.5.5 0 0 1 0 .858l-8.528 5.117a.5.5 0 0 1-.757-.43V10.884ZM27.889 20.509h1.58v-3.197h1.611l1.713 3.197h1.764l-1.887-3.46c1.027-.414 1.568-1.292 1.568-2.477 0-1.666-1.074-2.79-3.077-2.79h-3.273v8.727ZM29.468 16v-2.898h1.45c1.18 0 1.708.541 1.708 1.47 0 .93-.528 1.428-1.7 1.428H29.47ZM39.797 17.756c0 .997-.712 1.491-1.394 1.491-.741 0-1.235-.524-1.235-1.355v-3.929h-1.543v4.168c0 1.572.895 2.463 2.182 2.463.98 0 1.67-.516 1.969-1.249h.068v1.164h1.495v-6.546h-1.542v3.793ZM44.468 16.673c0-.946.571-1.491 1.385-1.491.797 0 1.274.524 1.274 1.397v3.93h1.543V16.34c.004-1.568-.89-2.463-2.241-2.463-.98 0-1.654.469-1.952 1.197H44.4v-1.112h-1.474v6.546h1.542v-3.836ZM53.215 20.509h1.542v-6.546h-1.542v6.546Zm.775-7.475c.49 0 .891-.375.891-.835 0-.465-.4-.84-.89-.84-.495 0-.895.375-.895.84 0 .46.4.835.894.835ZM57.886 16.673c0-.946.571-1.491 1.385-1.491.797 0 1.274.524 1.274 1.397v3.93h1.543V16.34c.004-1.568-.89-2.463-2.241-2.463-.98 0-1.654.469-1.952 1.197h-.077v-1.112h-1.474v6.546h1.542v-3.836ZM66.701 20.509h1.581v-2.95h1.67c2.016 0 3.098-1.21 3.098-2.889 0-1.666-1.07-2.889-3.076-2.889H66.7v8.728Zm1.581-4.25v-3.157h1.449c1.184 0 1.709.64 1.709 1.569 0 .928-.525 1.589-1.7 1.589h-1.458ZM77.117 20.636c1.917 0 3.136-1.35 3.136-3.375 0-2.028-1.219-3.383-3.136-3.383-1.918 0-3.136 1.355-3.136 3.383 0 2.024 1.218 3.375 3.136 3.375Zm.008-1.235c-1.06 0-1.58-.947-1.58-2.144s.52-2.156 1.58-2.156c1.044 0 1.564.959 1.564 2.156s-.52 2.144-1.564 2.144ZM86.736 15.693c-.213-1.108-1.1-1.815-2.634-1.815-1.576 0-2.65.775-2.646 1.986-.004.954.584 1.585 1.84 1.845l1.117.234c.601.132.883.375.883.746 0 .447-.486.784-1.22.784-.707 0-1.167-.307-1.299-.895l-1.504.145c.192 1.202 1.201 1.913 2.808 1.913 1.636 0 2.791-.848 2.795-2.088-.004-.933-.605-1.504-1.84-1.772l-1.117-.24c-.665-.148-.929-.378-.925-.758-.004-.443.486-.75 1.13-.75.711 0 1.086.388 1.206.819l1.406-.154ZM91.417 13.963h-1.291v-1.568h-1.543v1.568h-.929v1.193h.93v3.64c-.01 1.231.885 1.836 2.044 1.802a3.1 3.1 0 0 0 .908-.153l-.26-1.206c-.085.02-.26.06-.451.06-.388 0-.7-.137-.7-.76v-3.383h1.292v-1.193ZM92.707 20.509h1.543v-3.98c0-.806.537-1.351 1.201-1.351.652 0 1.1.438 1.1 1.112v4.219h1.513v-4.083c0-.737.439-1.248 1.184-1.248.622 0 1.117.366 1.117 1.176v4.155h1.547v-4.394c0-1.462-.844-2.237-2.046-2.237-.95 0-1.675.469-1.964 1.197h-.069c-.251-.741-.886-1.197-1.768-1.197-.878 0-1.534.451-1.807 1.197h-.076v-1.112h-1.475v6.546ZM105.36 20.64c1.027 0 1.641-.48 1.922-1.03h.051v.899h1.483v-4.381c0-1.73-1.41-2.25-2.659-2.25-1.376 0-2.433.614-2.774 1.807l1.44.204c.154-.447.588-.83 1.342-.83.716 0 1.108.366 1.108 1.01v.025c0 .443-.464.464-1.619.588-1.27.136-2.484.515-2.484 1.99 0 1.287.942 1.969 2.19 1.969Zm.401-1.133c-.644 0-1.104-.294-1.104-.86 0-.593.516-.84 1.206-.938.405-.056 1.214-.158 1.415-.32v.771c0 .73-.588 1.347-1.517 1.347ZM111.91 16.673c0-.946.571-1.491 1.385-1.491.797 0 1.274.524 1.274 1.397v3.93h1.543V16.34c.004-1.568-.891-2.463-2.242-2.463-.98 0-1.653.469-1.952 1.197h-.076v-1.112h-1.475v6.546h1.543v-3.836Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
BIN
products/idn/api/img/diff-changes.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
products/idn/api/img/pull-changes.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
62
products/idn/api/postman-collections.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: postman-collections
|
||||
title: Postman Collections
|
||||
pagination_label: Postman Collections
|
||||
sidebar_label: Postman Collections
|
||||
sidebar_position: 6
|
||||
sidebar_class_name: postmanCollections
|
||||
keywords: ['postman']
|
||||
description: How to run the APIs in Postman.
|
||||
tags: ['postman']
|
||||
---
|
||||
|
||||
[Postman](https://www.postman.com/) is a platform you can use to design, build, test, and iterate your APIs. Postman users and teams can create public workspaces they can use to make it easy to access their API collections and environments and get started. SailPoint maintains a [public workspace for the IdentityNow API collections](https://www.postman.com/sailpoint/workspace/identitynow). You can use this workspace to access all the IDN API collections and stay up to date.
|
||||
|
||||
## Run in Postman
|
||||
|
||||
Each IDN API version is broken out into a separate collection within the workspace. The following table lists the available IDN API collections. To import a collection into your workspace, select the 'Run in Postman' button for your desired version. Doing so forks the collection into your workspace.
|
||||
|
||||
| API | Postman Collection |
|
||||
|------|----------------------------|
|
||||
| V3 API | [](https://app.getpostman.com/run-collection/23226990-5f6a4855-8012-406f-9456-c8fa6311b080?action=collection%2Ffork&collection-url=entityId%3D23226990-5f6a4855-8012-406f-9456-c8fa6311b080%26entityType%3Dcollection%26workspaceId%3D80af54be-a333-4712-af5e-41aa9eccbdd0) |
|
||||
| Beta API | [](https://god.gw.postman.com/run-collection/23226990-18091672-ede8-4a27-a1b8-251d4bda2da1?action=collection%2Ffork&collection-url=entityId%3D23226990-18091672-ede8-4a27-a1b8-251d4bda2da1%26entityType%3Dcollection%26workspaceId%3D80af54be-a333-4712-af5e-41aa9eccbdd0) |
|
||||
| V2 API | [](https://god.gw.postman.com/run-collection/23226990-624bf09b-7d1b-4ee6-9833-4b581b41db40?action=collection%2Ffork&collection-url=entityId%3D23226990-624bf09b-7d1b-4ee6-9833-4b581b41db40%26entityType%3Dcollection%26workspaceId%3D80af54be-a333-4712-af5e-41aa9eccbdd0) |
|
||||
| cc/private API | [](https://god.gw.postman.com/run-collection/23226990-4ec40b38-cdac-44bf-a07c-8606895d2233?action=collection%2Ffork&collection-url=entityId%3D23226990-4ec40b38-cdac-44bf-a07c-8606895d2233%26entityType%3Dcollection%26workspaceId%3D80af54be-a333-4712-af5e-41aa9eccbdd0) |
|
||||
| SaaS Connectivity | [](https://god.gw.postman.com/run-collection/23226990-a0b5c429-d8dd-4fe2-a4a2-eb7ff85322ef?action=collection%2Ffork&collection-url=entityId%3D23226990-a0b5c429-d8dd-4fe2-a4a2-eb7ff85322ef%26entityType%3Dcollection%26workspaceId%3D80af54be-a333-4712-af5e-41aa9eccbdd0) |
|
||||
|
||||
You can also fork a collection by selecting the ellipses to the right of the collection and selecting 'Create a fork.'
|
||||
|
||||
When you fork the collection, it is recommended that you check the 'Watch original collection' checkbox to get notifications when there are changes to the collection. You can then pull the changes to merge them and stay up to date.
|
||||
|
||||
## Update your collections
|
||||
|
||||
SailPoint is often making improvements to the IDN API collections. To keep your workspace in sync with updates to one of SailPoint's public collections, select the ellipse to the right of the collection, and select 'Pull changes'.
|
||||
|
||||
If there are no changes, you're up to date. If there are changes, the screen lists the changes you're pulling. Select 'Pull changes' again to pull them in.
|
||||
|
||||
## Configure your environment
|
||||
|
||||
The SailPoint workspace provides an environment, a set of variables you can use in your requests, that you can fork and pull changes from to stay up to date the same way you can with collections.
|
||||
|
||||
To send API requests in Postman, you must authenticate to the APIs. To authenticate to the APIs, you must specify these variables in your Postman environment:
|
||||
|
||||
| Environment Variable | Required | Description |
|
||||
| ----------- | ----------- | ----------- |
|
||||
| tenant | Yes | Your IDN tenant, typically your company's name |
|
||||
| clientId | Yes | The client ID for the API client or personal access token |
|
||||
| clientSecret | Yes | The client secret for the API client or personal access token |
|
||||
| domain | No | This optional field is only necessary for those who have a domain in their API URL that isn't "identitynow". |
|
||||
|
||||
:::caution
|
||||
|
||||
Don't specify your baseUrl in your environment variables. When you fork an API collection, the baseUrl is automatically set as https://{{tenant}}.api.{{domain}}.com. Setting your baseURl in your environment variables may interfere with this process.
|
||||
|
||||
:::
|
||||
|
||||
Once you have configured your environment, you can start using Postman with all the endpoints provided in the collections.
|
||||
|
||||
:::tip
|
||||
|
||||
Anything you want to change about the Postman collection? We love feedback! Discuss the Postman collection [here](https://developer.sailpoint.com/discuss/t/official-identitynow-postman-workspace/6153).
|
||||
|
||||
:::
|
||||
@@ -184,7 +184,7 @@ Examples:
|
||||
|
||||
:::
|
||||
|
||||
### Sorting Results
|
||||
## Sorting Results
|
||||
|
||||
Result sorting is supported with the standard `sorters` parameter. Its syntax is a set of comma-separated field names. You may optionally prefix each field name with a "-" character, indicating that the sort is descending based on the value of that field. Otherwise, the sort is ascending.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ tags: ['Event Triggers', 'Available Event Triggers', 'Fire and Forget']
|
||||
|
||||
Users can subscribe to Saved Searches and receive an email of a report generated from the saved search. For example, a user can save a search query called "Identities with upcoming end dates" and create a subscription to receive a daily report showing identities with an end date within 10 days from the current date. This event trigger can also notify an external HTTP application that a report generated from a saved search subscription is available to be processed.
|
||||
|
||||
Saved Search Completed events occur based on the schedules set for saved search subscriptions. For example, if you have a scheduled saved search for Monday, Tuesday, Wednesday, Thursday, Friday at 6:00 GMT, your HTTP endpoint will also receive a notification at those times. This can be set using the `schedule` object in the [create scheduled search endpoint](/idn/api/v3/scheduled-search-create).
|
||||
Saved Search Completed events occur based on the schedules set for saved search subscriptions. For example, if you have a scheduled saved search for Monday, Tuesday, Wednesday, Thursday, Friday at 6:00 GMT, your HTTP endpoint will also receive a notification at those times. This can be set using the `schedule` object in the [create scheduled search endpoint](/idn/api/v3/create-scheduled-search).
|
||||
|
||||
To receive this event when a saved search query does not have any results, set `emailEmptyResults` to `TRUE`. You can also set the expiration date in the `expiration` field within the `schedule` object. Your HTTP endpoint will stop receiving these events when the scheduled search expires.
|
||||
|
||||
|
||||
@@ -68,4 +68,4 @@ This is an example input from this trigger:
|
||||
## Additional Information and Links
|
||||
|
||||
- **Trigger Type**: [FIRE_AND_FORGET](../trigger-types.md#fire-and-forget)
|
||||
- [Input schema](https://developer.sailpoint.com/apis/beta/#section/VA-Cluster-Status-Change-Event-Event-Trigger-Input)
|
||||
<!--[Input schema] (https://developer.sailpoint.com/apis/beta/#section/VA-Cluster-Status-Change-Event-Event-Trigger-Input) -->
|
||||
|
||||
@@ -20,7 +20,22 @@ This is an early access event trigger. Please contact support to have it enabled
|
||||
|
||||

|
||||
|
||||
Identity deleted events occur when an identity's associated account is deleted from the identity's authoritative source. After accounts are aggregated and the identity refresh process finds an identity that is not correlated to an account, the associated identity is deleted from IdentityNow. For more information, see [Configuring Correlation](https://community.sailpoint.com/t5/Connectors/Configuring-Correlation/ta-p/74045). The Identity deleted event contains any identity attributes as they are configured in the identity profile. For more information, see [Mapping Identity Profiles](https://community.sailpoint.com/t5/Admin-Help/Mapping-Identity-Profiles/ta-p/77877).
|
||||
Identity deleted event will occur when an identity meets all of the following requirements:
|
||||
|
||||
- No correlated accounts
|
||||
- Not an owner of a role, access profile, application, source, or taskResult
|
||||
- Not an owner or requester of a workItem
|
||||
- Not a protected account or manager
|
||||
- No assigned capabilities (ex. not an assigned cert reviewer)
|
||||
- Not involved in any active certification as a target (its access is not being certified)
|
||||
|
||||
After accounts are aggregated and the identity refresh process finds an identity that meets the above criteria, the associated identity is deleted from IdentityNow. For more information, see [Configuring Correlation](https://community.sailpoint.com/t5/Connectors/Configuring-Correlation/ta-p/74045). The Identity deleted event contains any identity attributes as they are configured in the identity profile. For more information, see [Mapping Identity Profiles](https://community.sailpoint.com/t5/Admin-Help/Mapping-Identity-Profiles/ta-p/77877).
|
||||
|
||||
:::info
|
||||
|
||||
IdentityNow will **hide** an identity from the identity list in the UI when the authoritative account is removed. This does not necessarily mean that the identity has been deleted. The identity will only be deleted when the above criteria are met. The deletion task run each night, so there will be a delay from when the criteria are met to when the identity will actually be deleted.
|
||||
|
||||
:::
|
||||
|
||||
This event trigger provides a flexible way to extend joiner-mover-leaver processes. This provides more proactive governance and ensures users can quickly get necessary access when they enter your organization.
|
||||
|
||||
|
||||
@@ -23,17 +23,9 @@ Network bandwidth and processing power come at a cost, especially when you are u
|
||||
|
||||
Event trigger filters are constructed using a **Jayway** JSONpath expression. See the following tables for a list of operators that can be used in a trigger filter.
|
||||
|
||||
:::info Update
|
||||
|
||||
SailPont's Workflow tool uses **Goessner** JSONpath when selecting variables to use in actions and operators. Please read the [Goessner](https://goessner.net/articles/JsonPath/) documentation to learn more about the supported operators.
|
||||
|
||||
Although variable selection in Workflows users Goessner, the trigger filter field in Workflows still follows the Jayway operators listed below.
|
||||
|
||||
:::
|
||||
|
||||
### Expressions
|
||||
|
||||
JSONPath expressions specify a path to an element or array of elements in a JSON structure. Expressions are used to select data in a JSON structure to check for the existence of attributes or to narrow down the data where the filter logic is applied.
|
||||
Expressions specify a path to an element or array of elements in a JSON structure. Expressions are used to select data in a JSON structure to check for the existence of attributes or to narrow down the data where the filter logic is applied.
|
||||
|
||||
| Expression | Description | Example |
|
||||
| --- | --- | --- |
|
||||
@@ -50,9 +42,17 @@ JSONPath expressions specify a path to an element or array of elements in a JSON
|
||||
| ?() | **Filter expression** - Applies a filter expression. | $[?($.identity.name == "john.doe")] |
|
||||
| () | **Script expression** - Applies a script expression. | $.changes[(@.length-1)] |
|
||||
|
||||
### Functions
|
||||
|
||||
Functions can be invoked at the tail end of a path - the input to a function is the output of the path expression. The function output is dictated by the function itself.
|
||||
|
||||
| Function | Description | Output type | Example |
|
||||
| --- | --- | --- | --- |
|
||||
| length() | Provides the length of an array | Integer | $[?($.changes.length() >= 3)] |
|
||||
|
||||
### Operators
|
||||
|
||||
JSONPath operators provide more options to filter JSON structures.
|
||||
Operators provide more options to filter JSON structures.
|
||||
|
||||
| Operator | Description | Example |
|
||||
| --- | --- | --- |
|
||||
@@ -62,6 +62,13 @@ JSONPath operators provide more options to filter JSON structures.
|
||||
| >= | **Greater than or equal to** - Evaluates to `true` if the left operand is greater than or equal to the right operand. | $[?($.attributes.created >= '2020-04-27T16:48:33.597Z')] |
|
||||
| < | **Less than** - Evaluates to `true` if the left operand is less than the right operand. | $[?($.attributes.created < '2020-04-27T16:48:33.200Z')] |
|
||||
| <= | **Less than or equal to** - Evaluates to `true` if the left operand is less than or equal to the right operand. | $[?($.attributes.created <= '2020-04-27T16:48:33.200Z')] |
|
||||
| =~ | **Regular expression** - Evaluates to `true` if the left operand matches the regular expression. | $.changes[?(@.attribute == "department" && @.newValue =~ /US.*Support/i)] |
|
||||
| in | **In** - Evaluates to `true` if the left operand exists in the list of values on the right. | $.changes[?(@.attribute == 'department' && @.newValue in ['sales','engineering'])] |
|
||||
| nin | **Not in** - Evaluates to `true` if the left operand **does not** exist in the list of values on the right. | $.changes[?(@.attribute == 'department' && @.newValue nin ['sales','engineering'])] |
|
||||
| subsetof | **Subset of** - Evaluates to `true` if the left operand is a subset of the right. | $[?($.warnings subsetof ['Account skipped','Invalid account'])] |
|
||||
| anyof | **Any of** - Evaluates to `true` if the left operand has an intersection with the right. | $[?($.warnings anyof ['Account skipped','Invalid account'])] |
|
||||
| noneof | **None of** - Evaluates to `true` if the left operand **does not** have an intersection with the right. | $[?($.warnings anyof ['Account skipped','Invalid account'])] |
|
||||
| size | **Size** - Evaluates to `true` if the size of the left (array or string) matches the right. | $[?($.warnings size 1] |
|
||||
| && | Logical **AND** operator that evaluates `true` only if both conditions are `true`. | $.changes[?(@.attribute == "cloudLifecycleState" && @.newValue == "terminated")] |
|
||||
| ! | **Not** - Negates the boolean expression. | $.identity.attributes[?(!@.alternateEmail)] |
|
||||
| \|\| | Logical **OR** operator that evaluates `true` if at least one condition is `true`. | $.changes[?(@.attribute == "cloudLifecycleState" \|\| @.attribute == "department")] |
|
||||
@@ -69,9 +76,7 @@ JSONPath operators provide more options to filter JSON structures.
|
||||
|
||||
### Developing Filters
|
||||
|
||||
Developing a filter can be faster when you use a tool like an online [JSONpath editor](https://jsonpath.herokuapp.com/). These tools can provide quick feedback on your filter, allowing you to focus on the exact filter expression you want before testing it on a trigger.
|
||||
|
||||
Start by opening a [JSONpath editor](https://jsonpath.herokuapp.com/) in your browser. Make sure that the correct implementation is selected if there is more than one option. In the case of event trigger filters, you will want to select the **Jayway** option. You can then paste in an example trigger input and start crafting your JSONpath expression.
|
||||
Developing a filter can be faster when you use a tool like an online [JSONpath editor](https://www.javainuse.com/jsonpath). These tools can provide quick feedback on your filter, allowing you to focus on the exact filter expression you want before testing it on a trigger. Just paste an example of your event trigger input and start crafting an expression to see its result.
|
||||
|
||||

|
||||
|
||||
@@ -124,7 +129,7 @@ To validate a filter using the UI, subscribe to a new event trigger or edit an e
|
||||
|
||||
### Validating Filters Using the API
|
||||
|
||||
You can validate a trigger filter by using the [validate filter](/idn/api/beta/validate-filter) API endpoint. You must escape any double quotes, as seen in the example payload in the API description. Also, you must provide a sample input for the validation engine to run against. It is best to use the input example included in the input/output schemas for the event trigger you want to apply your filter to. Refer to [this table](/idn/api/beta/triggers#available-event-triggers) to find the schema of your event trigger. This is an example request:
|
||||
You can validate a trigger filter by using the [validate filter](/idn/api/beta/validate-subscription-filter) API endpoint. You must escape any double quotes, as seen in the example payload in the API description. Also, you must provide a sample input for the validation engine to run against. It is best to use the input example included in the input/output schemas for the event trigger you want to apply your filter to. Refer to [this table](/idn/api/beta/triggers#available-event-triggers) to find the schema of your event trigger. This is an example request:
|
||||
|
||||
```text
|
||||
POST https://{tenant}.api.identitynow.com/beta/trigger-subscriptions/validate-filter
|
||||
@@ -176,4 +181,4 @@ If SailPoint accepts your trigger filter, you must test whether it actually work
|
||||
|
||||
Once you fire off a test event, monitor your webhook.site webpage for an incoming event. If the filter matches the test input, you will an event come in. If the filter does not match the input, then it will nott fire. Test both scenarios to make sure your filter is not always evaluating to `true`, and that it will indeed evaluate to `false` under the correct circumstances. For example, the filter `$[?($.identity.name contains "john")]` will match the test event for Identity Attributes Changed and you will see an event in webhook.site, but you also want to make sure that `$[?($.identity.name contains "archer")]` doesn't fire because the test input is always the same.
|
||||
|
||||
If you want to control the test input to validate your filter against a more robust set of data, use the [test invocation](/idn/api/beta/start-test-invocation) API endpoint.
|
||||
If you want to control the test input to validate your filter against a more robust set of data, use the [test invocation](/idn/api/beta/start-test-trigger-invocation) API endpoint.
|
||||
|
||||
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 152 KiB |
@@ -190,4 +190,4 @@ POST `https://{tenant}.api.identitynow.com/beta/trigger-invocations/e9103ca9-02c
|
||||
|
||||
## Trigger Invocation Status
|
||||
|
||||
To check the status of a particular trigger invocation, you can use the [list invocation statuses](/idn/api/beta/list-invocation-status) endpoint. The status endpoint works for both `REQUEST_RESPONSE` and `FIRE_AND_FORGET` triggers. However, the status of `FIRE_AND_FORGET` trigger invocations will contain null values in their `completeInvocationInput` since `FIRE_AND_FORGET` triggers don't need a response to complete.
|
||||
To check the status of a particular trigger invocation, you can use the [list invocation statuses](/idn/api/beta/list-trigger-invocation-status) endpoint. The status endpoint works for both `REQUEST_RESPONSE` and `FIRE_AND_FORGET` triggers. However, the status of `FIRE_AND_FORGET` trigger invocations will contain null values in their `completeInvocationInput` since `FIRE_AND_FORGET` triggers don't need a response to complete.
|
||||
|
||||
@@ -21,7 +21,7 @@ The easiest way to send a test event to your subscribing service is to use the *
|
||||
|
||||
Doing so sends a test event to your subscribing service, using the default example payload for the specific trigger you are subscribing to. This is an easy way to validate that your service can receive events, but it lacks the ability to modify the event payload to test your filter against different payloads. However, there is an API endpoint you can use to modify the test payload.
|
||||
|
||||
If you want to control the test input to validate your filter against a more robust set of data, you can use the [test invocation](/idn/api/beta/start-test-invocation) API endpoint. You can use this API to send an input payload with any values that you want. This is an example of an invocation of this API:
|
||||
If you want to control the test input to validate your filter against a more robust set of data, you can use the [test invocation](/idn/api/beta/start-test-trigger-invocation) API endpoint. You can use this API to send an input payload with any values that you want. This is an example of an invocation of this API:
|
||||
|
||||
```text
|
||||
POST `https://{tenant}.api.identitynow.com/beta/trigger-invocations/test`
|
||||
@@ -69,7 +69,7 @@ Check the **Created** date with the time you sent the test events. If they are b
|
||||
|
||||

|
||||
|
||||
You can also view the activity log by using the [list latest invocation statuses](/idn/api/beta/list-invocation-status) endpoint.
|
||||
You can also view the activity log by using the [list latest invocation statuses](/idn/api/beta/list-trigger-invocation-status) endpoint.
|
||||
|
||||
### Filter Issues
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ sidebar_label: BuildMap Rule
|
||||
sidebar_class_name: buildMapRule
|
||||
keywords: ['cloud', 'rules']
|
||||
description: This rule manipulates raw input data provided by the rows and columns in a file and builds a map from the incoming data.
|
||||
slug: /docs/rules/connector-rules/buildmap-rule
|
||||
slug: /docs/rules/cloud-rules/buildmap-rule
|
||||
tags: ['Rules']
|
||||
---
|
||||
|
||||
@@ -16,12 +16,16 @@ tags: ['Rules']
|
||||
|
||||
This rule manipulates raw input data provided by the rows and columns in a file and builds a map from the incoming data.
|
||||
|
||||
:::info
|
||||
This rule runs in the cloud, but it's really a connector rule because it executes against the DelimitedFileConnector.
|
||||
:::
|
||||
|
||||
## Execution
|
||||
|
||||
- **Connector Execution** - This rule executes within the virtual appliance. It may offer special abilities to perform connector-related functions, and it may offer managed connections to sources.
|
||||
- **Logging** - Logging statements are viewable within the ccg.log on the virtual appliance, and they are viewable by SailPoint personnel.
|
||||
- **Cloud Execution** - This rule executes in the IdentityNow cloud and it has read-only access to IdentityNow data models, but it doesn't have access to on-premise sources or connectors.
|
||||
- **Logging** - Logging statements are currently only visible to SailPoint personnel.
|
||||
|
||||

|
||||

|
||||
|
||||
## Input
|
||||
|
||||
@@ -39,12 +39,12 @@ Connector Rules are directly editable with the [Connector Rule REST APIs](https:
|
||||
|
||||
| Name | Path |
|
||||
| --- | --- |
|
||||
| [List Connector Rules](https://developer.sailpoint.com/apis/beta/#operation/getConnectorRuleList) | `GET /beta/connector-rules/` |
|
||||
| [Get Connector Rule](https://developer.sailpoint.com/apis/beta/#operation/getConnectorRule) | `GET /beta/connector-rules/{id}` |
|
||||
| [Create Connector Rule](https://developer.sailpoint.com/apis/beta/#operation/createConnectorRule) | `POST /beta/connector-rules/` |
|
||||
| [Update Connector Rule](https://developer.sailpoint.com/apis/beta/#operation/updateConnectorRule) | `PUT /beta/connector-rules/{id}` |
|
||||
| [Delete Connector Rule](https://developer.sailpoint.com/apis/beta/#operation/deleteConnectorRule) | `DELETE /beta/connector-rules/{id}` |
|
||||
| [Validate Connector Rule](https://developer.sailpoint.com/apis/beta/#operation/validateConnectorRule) | `POST /beta/connector-rules/validate` |
|
||||
| [List Connector Rules](/idn/api/beta/get-connector-rule-list) | `GET /beta/connector-rules/` |
|
||||
| [Get Connector Rule](/idn/api/beta/get-connector-rule) | `GET /beta/connector-rules/{id}` |
|
||||
| [Create Connector Rule](/idn/api/beta/create-connector-rule) | `POST /beta/connector-rules/` |
|
||||
| [Update Connector Rule](/idn/api/beta/update-connector-rule) | `PUT /beta/connector-rules/{id}` |
|
||||
| [Delete Connector Rule](/idn/api/beta/delete-connector-rule) | `DELETE /beta/connector-rules/{id}` |
|
||||
| [Validate Connector Rule](/idn/api/beta/validate-connector-rule) | `POST /beta/connector-rules/validate` |
|
||||
|
||||
SailPoint architectural optimizations have added resiliency and protections against malformed or long-running rules. These APIs also offer built-in protection and checking against potentially harmful code. For more information, see [Rule Code Restrictions](../../rules/index.md#rule-code-restrictions).
|
||||
|
||||
|
||||
@@ -500,6 +500,23 @@ String value, String sortAttribute)
|
||||
*/
|
||||
public int countIdentitiesBySearchableIdentityAttribute(String attributeName, String operation, String value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to call LDAP type connectors to look for
|
||||
* unique values. This method calls the connector with a specific search filter
|
||||
* based on the attributeName and value passed into the method.
|
||||
* Any returned value is considered non-unique.
|
||||
*
|
||||
* @param identityNameOrId The name or ID of the identity you are using
|
||||
* @param applicationNameOrId The name or ID of the source you are targeting
|
||||
* @param attributeName The name of the attribute you want to validate
|
||||
* @param attributeValue The value of the attribute you want to validate
|
||||
*
|
||||
* @return true if the value is unique AND false otherwise. If the application or identity can't be found, an
|
||||
* IllegalStateException will be thrown.
|
||||
*
|
||||
*/
|
||||
public boolean isUniqueLDAPValue(String identityNameOrId, String applicationNameOrId, String attributeName, String attributeValue)
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
@@ -25,11 +25,13 @@ This document is intended for technically proficient administrators, implementer
|
||||
|
||||
| **Object** | **Object Type** | **Export** | **Import** |
|
||||
| :-- | :-- | :-- | :-- |
|
||||
| Event Trigger Subscriptions | `TRIGGER_SUBSCRIPTION` |  |  |
|
||||
| Identity Profiles | `IDENTITY_PROFILE` |  |  |
|
||||
| Rules | `RULE` |  |  |
|
||||
| Sources | `SOURCE` |  |  |
|
||||
| Transforms | `TRANSFORM` |  |  |
|
||||
| Authentication Configuration | `AUTH_ORG` | ✅ | ❌ |
|
||||
| Event Trigger Subscriptions | `TRIGGER_SUBSCRIPTION` | ✅ | ✅ |
|
||||
| Governance Groups | `GOVERNANCE_GROUP` | ✅ | ❌ |
|
||||
| Identity Profiles | `IDENTITY_PROFILE` | ✅ | ✅ |
|
||||
| Rules | `RULE` | ✅ | ✅|
|
||||
| Sources | `SOURCE` | ✅ | ✅ |
|
||||
| Transforms | `TRANSFORM` | ✅ | ✅ |
|
||||
|
||||
:::tip
|
||||
|
||||
@@ -77,7 +79,7 @@ The available supported objects are also available via REST API! See List Config
|
||||
|
||||
## API Reference Guide
|
||||
|
||||
| **Description** | **REST API End-Point** |
|
||||
| **Description** | **REST API Endpoint** |
|
||||
| :------------------ | :----------------------------------------- |
|
||||
| List Config Objects | `GET /beta/sp-config/config-objects` |
|
||||
| Export Objects | `POST /beta/sp-config/export` |
|
||||
|
||||
@@ -18,6 +18,7 @@ tags: ['Connectivity', 'Connector Command']
|
||||
|
||||
```javascript
|
||||
{
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
|
||||
@@ -30,6 +30,7 @@ tags: ['Connectivity', 'Connector Command']
|
||||
|
||||
```javascript
|
||||
{
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
|
||||
@@ -18,6 +18,7 @@ tags: ['Connectivity', 'Connector Command']
|
||||
|
||||
```javascript
|
||||
{
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
@@ -63,6 +64,12 @@ async getAllAccounts(): Promise<AirtableAccount[]> {
|
||||
}
|
||||
```
|
||||
|
||||
:::caution Important
|
||||
|
||||
IDN will throw a connection timeout error if your connector doesn't respond within 3 minutes, and there are memory limitations involved with aggregating data. To prevent large memory utilization or timeout errors, you should set up your connectors to send data to IDN as it's retrieved from your source system. For more details and an example, refer to [Connector Timeouts](../in-depth/connector-timeouts.md).
|
||||
|
||||
:::
|
||||
|
||||
The following code snippet from [index.ts](https://github.com/sailpoint-oss/airtable-example-connector/blob/main/src/index.ts) shows how to register the account list command on the connector object:
|
||||
|
||||
```javascript
|
||||
@@ -71,7 +78,7 @@ export const connector = async () => {
|
||||
// Get connector source config
|
||||
const config = await readConfig()
|
||||
|
||||
// Use the vendor SDK, or implement own client as necessary, to initialize a client
|
||||
// Use the vendor SDK or implement own client as necessary to initialize a client
|
||||
const airtable = new AirtableClient(config)
|
||||
|
||||
return createConnector()
|
||||
|
||||
@@ -17,6 +17,7 @@ tags: ['Connectivity', 'Connector Command']
|
||||
### Example StdAccountReadInput
|
||||
|
||||
```javascript
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
|
||||
@@ -17,6 +17,7 @@ tags: ['Connectivity', 'Connector Command']
|
||||
### Example StdAccountUnlockInput
|
||||
|
||||
```javascript
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
|
||||
@@ -18,6 +18,7 @@ tags: ['Connectivity', 'Connector Command']
|
||||
|
||||
```javascript
|
||||
{
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
@@ -101,3 +102,7 @@ After the connector applies the operations defined in the input payload, the con
|
||||
You can test the account update command the way you test the [Account Create](./account-create.md) command. Follow the steps in “Testing in IdentityNow” from “Account Create” to set up an access profile and role. Be sure to run the aggregation so the account(s) are created in the target source. Once the account(s) are created in the target source, modify the access profile to grant an additional entitlement. Return to the role and click the ‘Update’ button in the upper right corner. Doing so triggers the account update command because the accounts are already created in the target source. Once the update is complete, ensure the account(s) have the additional entitlement.
|
||||
|
||||
Note: Testing the account update command for removing entitlements using this method does not work. You can remove the entitlement from the access profile and run an update, but IDN will not send an update command to the connector to remove the entitlement. We are looking for suggestions on how to test the removal of entitlements.
|
||||
|
||||
## Handling an account that is not found
|
||||
|
||||
If an account can't be found in the source system, IDN can recreate the account by using the ```ConnectorErrorType.NotFound``` error type. For details and implementation, refer to [Error Handling](../in-depth/error-handling.md#not-found-error-type).
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: change-password
|
||||
title: Change Password
|
||||
pagination_label: Change Password
|
||||
sidebar_label: Change Password
|
||||
keywords: ['connectivity', 'connectors', 'change password']
|
||||
description: Change password for an account on the source.
|
||||
slug: /docs/saas-connectivity/commands/change-password
|
||||
tags: ['Connectivity', 'Connector Command']
|
||||
---
|
||||
|
||||
| Input/Output | Data Type |
|
||||
| :----------- | :--------------------: |
|
||||
| Input | StdChangePasswordInput |
|
||||
| Output | StdChangePasswordOutput |
|
||||
|
||||
### Example StdChangePasswordInput
|
||||
|
||||
```javascript
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
}
|
||||
},
|
||||
"password": "newPassword"
|
||||
```
|
||||
|
||||
### Example StdChangePasswordOutput
|
||||
|
||||
```javascript
|
||||
{}
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
The change password command is triggered in IDN when a user changes their password through IDN. When this occurs, if your source has change password enabled, then you can change the user password on the source system through IDN.
|
||||
|
||||
## The Provisioning Plan
|
||||
|
||||
The change password command sends the password change event to your connector whenever a user changes their password through the Password Manager. Handling this even is as simple as implementing a method on the source system that updates a users password
|
||||
|
||||
```javascript
|
||||
.stdChangePassword(async (context: Context, input: StdChangePasswordInput, res: Response<StdChangePasswordOutput>) => {
|
||||
res.send(await myClient.changePassword(input.identity))
|
||||
})
|
||||
```
|
||||
|
||||
## Testing in IdentityNow
|
||||
|
||||
In order to test in IdentityNow, the source application must be configured so that it is able to accept password change requests through the Password Manager. Once this setup is complete, you can log in as a user whose identity exists in the configured application and change their password in the Password Manager.
|
||||
@@ -26,6 +26,7 @@ tags: ['Connectivity', 'Connector Command']
|
||||
|
||||
```javascript
|
||||
{
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "administrator"
|
||||
@@ -103,3 +104,8 @@ private buildStandardObject(): StdEntitlementReadOutput | StdEntitlementListOutp
|
||||
}
|
||||
}
|
||||
```
|
||||
:::caution Important
|
||||
|
||||
IDN will throw a connection timeout error if your connector doesn't respond within 3 minutes, and there are memory limitations involved with aggregating data. To prevent large memory utilization or timeout errors, you should set up your connectors to send data to IDN as it's retrieved from your source system. For more details and an example, refer to [Connector Timeouts](../in-depth/connector-timeouts.md).
|
||||
|
||||
:::
|
||||
@@ -24,6 +24,7 @@ At this time Entitlement Read is not triggered from IDN for any specific workflo
|
||||
|
||||
```javascript
|
||||
{
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "john.doe"
|
||||
@@ -37,6 +38,7 @@ At this time Entitlement Read is not triggered from IDN for any specific workflo
|
||||
|
||||
```javascript
|
||||
{
|
||||
"identity": "john.doe",
|
||||
"key": {
|
||||
"simple": {
|
||||
"id": "administrator"
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
id: connector-spec-card
|
||||
title: Card
|
||||
pagination_label: Card
|
||||
sidebar_label: Card
|
||||
keywords: ['connectivity', 'connectors','connector-spec', 'card']
|
||||
description: Details on using the card item
|
||||
slug: /docs/saas-connectivity/connector-spec/card
|
||||
tags: ['Connectivity', 'Connector Spec']
|
||||
---
|
||||
|
||||
## How to use the card type in the connector spec
|
||||
You can use the `card` type to specify cards that allow users to add/copy/delete and enter a subMenu to make changes to more card details.
|
||||
|
||||
When you create a card, you must specify the fields the cardSubMenu will use to generate the title and subtitle, as shown in the following example.
|
||||
|
||||
In this example, clicking the ```Add table``` button opens a dialog, and the values entered for the ```Table Information``` and ```Airtable Id``` will populate the cards ```title``` and ```subtitle```.
|
||||
|
||||
### Example card item type
|
||||
|
||||
```javascript
|
||||
{
|
||||
"key": "tableParameters",
|
||||
"label": "AddTable",
|
||||
"titleKey": "tableName",
|
||||
"subtitleKey": "tableId",
|
||||
"indexKey": "sequenceNumberForTable",
|
||||
"dragNDropEnabled": true,
|
||||
"deleteButton": true,
|
||||
"editButton": true,
|
||||
"addButton": true,
|
||||
"copyButton": true,
|
||||
"buttonLabel": "Add Table",
|
||||
"type": "cardList",
|
||||
"subMenus": [
|
||||
{
|
||||
"label": "Table Information",
|
||||
"items": [
|
||||
{
|
||||
"key": "tableName",
|
||||
"label": "Airtable Name",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"helpKey": "Must be a unique name"
|
||||
},
|
||||
{
|
||||
"key": "tableId",
|
||||
"label": "Airtable Id",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"helpKey": "Must be a unique name"
|
||||
},
|
||||
{
|
||||
"key": "tableType",
|
||||
"type": "radio",
|
||||
"label": "Table data type",
|
||||
"required": true,
|
||||
"options": [
|
||||
{
|
||||
"label": "Accounts",
|
||||
"value": "accounts"
|
||||
},
|
||||
{
|
||||
"label": "Entitlements",
|
||||
"value": "entitlements"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -38,17 +38,25 @@ The following describes in detail the different fields in the connector spec:
|
||||
- **type:** This is always "section" - it indicates a new section on the page
|
||||
- **sectionTitle:** The large text title that will display for the section.
|
||||
- **sectionHelpMessage:** A description about the section that can help the user understand what it is used for and how to fill out the fields
|
||||
- **docLinkLabel:** An optional field that is the text that displays next to documentation help link.
|
||||
- **docLink:** The optional link that the docLinkLabel will direct to if clicked.
|
||||
- **key:** The name of the configuration item as it is referenced in code.
|
||||
- **label:** The name of the configuration item as it appears in the UI.
|
||||
- **required** (Optional): Set to 'false' by default. Valid values are 'true' or 'false.' You must populate required configuration items in the IDN source configuration wizard before continuing.
|
||||
- **type:** The configuration items' types. The following types are valid:
|
||||
- text
|
||||
- secret
|
||||
- url
|
||||
- email
|
||||
- number
|
||||
- secret
|
||||
- textarea
|
||||
- secrettextarea
|
||||
- checkbox
|
||||
- json
|
||||
- url
|
||||
- [radio](./connector-spec/radio)
|
||||
- [select](./connector-spec/select)
|
||||
- toggle
|
||||
- [list](./connector-spec/list)
|
||||
- [keyValue](./connector-spec/key-value)
|
||||
- [cardList](./connector-spec/card)
|
||||
- **accountSchema:** The schema for an account in IDN populated by data from the source.
|
||||
- **displayAttribute:** Identifies the attribute (defined below) used to map to `Account Name` in the IdentityNow account schema. This should be a unique value even though it is not required because the connector will use this value to correlate accounts in IDN to accounts in the source system.
|
||||
- **identityAttribute:** Identifies the attribute (defined below) used to map to `Account ID` in the IdentityNow account schema. This must be a globally unique identifier, such as email address, employee ID, etc.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: connector-spec-key-value
|
||||
title: Key Value
|
||||
pagination_label: Key Value
|
||||
sidebar_label: Key Value
|
||||
keywords: ['connectivity', 'connectors','connector-spec', 'keyValue']
|
||||
description: Details on using the key value item
|
||||
slug: /docs/saas-connectivity/connector-spec/key-value
|
||||
tags: ['Connectivity', 'Connector Spec']
|
||||
---
|
||||
|
||||
## How to use the key value type in the connector spec
|
||||
You can use the `keyValue` type to allow users to enter multiple key value items in a single entry box.
|
||||
|
||||
This is an example implementation:
|
||||
|
||||
### Example key value item type
|
||||
|
||||
```javascript
|
||||
{
|
||||
"key": "header",
|
||||
"label": "Header Values",
|
||||
"type": "keyValue",
|
||||
"keyValueKey": {
|
||||
"key": "key",
|
||||
"label": "Key",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"maxlength": "4096"
|
||||
},
|
||||
"keyValueValue": {
|
||||
"key": "value",
|
||||
"label": "Value",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"maxlength": "4096"
|
||||
}
|
||||
}
|
||||
```
|
||||

|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: connector-spec-list
|
||||
title: List
|
||||
pagination_label: List
|
||||
sidebar_label: List
|
||||
keywords: ['connectivity', 'connectors','connector-spec', 'list']
|
||||
description: Details on using the list item
|
||||
slug: /docs/saas-connectivity/connector-spec/list
|
||||
tags: ['Connectivity', 'Connector Spec']
|
||||
---
|
||||
|
||||
## How to use the list type in the connector spec
|
||||
You can use the `list` type to allow users to enter multiple items in a single entry box.
|
||||
|
||||
This is an example implementation:
|
||||
|
||||
### Example list item type
|
||||
|
||||
```javascript
|
||||
{
|
||||
"key": "entitlements",
|
||||
"label": "Entitlements",
|
||||
"type": "list",
|
||||
"helpKey": "Add a list of entitlements to expose via your source"
|
||||
}
|
||||
```
|
||||

|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: connector-spec-radio
|
||||
title: Radio
|
||||
pagination_label: Radio
|
||||
sidebar_label: Radio
|
||||
keywords: ['connectivity', 'connectors','connector-spec', 'radio']
|
||||
description: Details on using the Radio item
|
||||
slug: /docs/saas-connectivity/connector-spec/radio
|
||||
tags: ['Connectivity', 'Connector Spec']
|
||||
---
|
||||
|
||||
## How to use the radio type in the connector spec
|
||||
You can use the `Rrdio` type to create radio buttons for users to interact with to select from a predefined set of values.
|
||||
|
||||
This is an example implementation:
|
||||
|
||||
### Example radio item type
|
||||
|
||||
```javascript
|
||||
{
|
||||
"key": "airtableURL",
|
||||
"type": "radio",
|
||||
"label": "Airtable URL",
|
||||
"required": true,
|
||||
"options": [
|
||||
{
|
||||
"label": "Standard",
|
||||
"value": "standard"
|
||||
},
|
||||
{
|
||||
"label": "Custom",
|
||||
"value": "custom"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
You can also create dependencies on other fields so they are hidden until the selection is made. This same type of dependency can be built into any field and linked by using the parentKey/parentValue fields.
|
||||
|
||||
### Example dependency on earlier select field
|
||||
|
||||
```javascript
|
||||
{
|
||||
"key": "baseUrl",
|
||||
"type": "text",
|
||||
"label": "Base URL",
|
||||
"parentKey": "airtableURL",
|
||||
"parentValue": "custom",
|
||||
"placeholder": "https://{your domain}",
|
||||
"required": true
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: connector-spec-select
|
||||
title: Select
|
||||
pagination_label: Select
|
||||
sidebar_label: Select
|
||||
keywords: ['connectivity', 'connectors','connector-spec', 'select']
|
||||
description: Details on using the select item
|
||||
slug: /docs/saas-connectivity/connector-spec/select
|
||||
tags: ['Connectivity', 'Connector Spec']
|
||||
---
|
||||
|
||||
## How to use the Select type in the connector spec
|
||||
You can use the Select type to create a dropdown for users to interact with to select from a predefined set of values.
|
||||
|
||||
This is an example implementation:
|
||||
|
||||
### Example select item type
|
||||
|
||||
```javascript
|
||||
{
|
||||
"key": "airtableURL",
|
||||
"type": "select",
|
||||
"label": "Airtable URL",
|
||||
"required": true,
|
||||
"options": [
|
||||
{
|
||||
"label": "Standard",
|
||||
"value": "standard"
|
||||
},
|
||||
{
|
||||
"label": "Custom",
|
||||
"value": "custom"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
You can also create dependencies on other fields so they are hidden until the selection is made. This same type of dependency can be built into any field and linked by using the parentKey/parentValue fields.
|
||||
|
||||
### Example dependency on earlier select field
|
||||
|
||||
```javascript
|
||||
{
|
||||
"key": "baseUrl",
|
||||
"type": "text",
|
||||
"label": "Base URL",
|
||||
"parentKey": "airtableURL",
|
||||
"parentValue": "custom",
|
||||
"placeholder": "https://{your domain}",
|
||||
"required": true
|
||||
}
|
||||
```
|
||||
BIN
products/idn/docs/identity-now/saas-connectivity/img/card.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
BIN
products/idn/docs/identity-now/saas-connectivity/img/list.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 51 KiB |
BIN
products/idn/docs/identity-now/saas-connectivity/img/radio.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
products/idn/docs/identity-now/saas-connectivity/img/select.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: connector-timeouts
|
||||
title: Connector Timeouts
|
||||
pagination_label: Connector Timeouts
|
||||
sidebar_label: Connector Timeouts
|
||||
sidebar_position: 1.2
|
||||
sidebar_class_name: connectorTimeouts
|
||||
keywords: ['connectivity', 'connectors', 'timeout']
|
||||
description: IdentityNow will throw an error if your connector does not send a response in 3 minutes. For connector commands that might take longer than 3 minutes, make sure to send data at regular intervals to prevent a timeout.
|
||||
slug: /docs/saas-connectivity/in-depth/connector-timeouts
|
||||
tags: ['Connectivity']
|
||||
---
|
||||
|
||||
An IdentityNow SaaS Connectivity connector will send a timeout error if it doesn't send a response within 3 minutes. If the connector is sending thousands of records, the record fetching process will likely exceed this timeout limit. If you are storing all those records in memory before sending them to IDN, you can run into memory utilization issues. To prevent these issues, you should paginate through your source data and send the data back in regular intervals.
|
||||
|
||||
This is an example of how to set up this pagination:
|
||||
|
||||
```javascript
|
||||
async getAccounts(res: Response<StdAccountListOutput>): Promise<boolean> {
|
||||
return this.airtableBase('Users').select(
|
||||
// in this case, each page will only be ten records, but this could be increased depending on your needs and the limits of the source connector
|
||||
{pageSize: 10}
|
||||
// each page will be called recursively until there are no more records to fetch, at which case the promise is fulfilled
|
||||
).eachPage((records, fetchNextPage) => {
|
||||
for (let record of records) {
|
||||
// this is the part that sends the data to IdentityNow. Since eachPage is called with just 10 records,
|
||||
// if there are 100 records total, we would send data back to IDN in 10 sets of 10 records.
|
||||
res.send({
|
||||
identity: record.id,
|
||||
attributes: {
|
||||
id: <string>record.get('id'),
|
||||
email: <string>record.get('email'),
|
||||
fullname: <string>record.get('fullname'),
|
||||
entitlements: <string[]>(record.get('entitlements') ? record.get('entitlements') : [])
|
||||
}
|
||||
})
|
||||
}
|
||||
fetchNextPage()
|
||||
|
||||
}).then(()=>{
|
||||
return true
|
||||
}).catch((err) => {
|
||||
throw new ConnectorError('error while fetching accounts')
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
@@ -6,12 +6,12 @@ sidebar_label: Error Handling
|
||||
sidebar_position: 3
|
||||
sidebar_class_name: errorHandling
|
||||
keywords: ['connectivity', 'connectors', 'error handling']
|
||||
description: Any time code can fail due to validation issues, connectivity or configuration errors, handle the error and provide information back to the user about what went wrong.
|
||||
description: If the code fails due to validation issues, connectivity, or configuration errors, you can handle the error and provide the user with information about what went wrong.
|
||||
slug: /docs/saas-connectivity/in-depth/error-handling
|
||||
tags: ['Connectivity']
|
||||
---
|
||||
|
||||
Any time code can fail due to validation issues, connectivity or configuration errors, handle the error and provide information back to the user about what went wrong. If you handle your errors properly, it will be easier to debug and pinpoint what happened in your connector when something goes wrong.
|
||||
If the code fails due to validation issues, connectivity or configuration errors, you can handle the error and provide the user with information about what went wrong. Properly handled errors make it easier to debug and identify what happened in your connector when something goes wrong.
|
||||
|
||||
## Connector Errors
|
||||
|
||||
@@ -43,6 +43,27 @@ export class AirtableClient {
|
||||
}
|
||||
```
|
||||
|
||||
## Not Found Error Type
|
||||
|
||||
The connector SDK offers a special error type of "Not Found". This error signals to IDN that the specific account is not in the source system. If the account should be in the source system, IDN will then call the connector ```std:account:create``` command to create the account.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```javascript
|
||||
.stdAccountUpdate(async (context: Context, input: StdAccountUpdateInput, res: Response<StdAccountUpdateOutput>) => {
|
||||
const account = await myClient.getAccount(input.identity)
|
||||
if (!account) {
|
||||
// account was not found, but identity now has the account and expects it to be there!
|
||||
// Send an error message to IdentityNow so the account is automatically created
|
||||
if (!account) {
|
||||
throw new ConnectorError("account is not found", ConnectorErrorType.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
... perform normal account update logic below
|
||||
|
||||
```
|
||||
|
||||
## Custom Errors
|
||||
|
||||
You can also create custom errors and use them in your code to give more meaningful and specific responses to error states. For example, when you are configuring your connector, it is recommended that you throw an `InvalidConfigurationError` instead of a generic ConnectorError. To do this, create the custom error:
|
||||
@@ -56,7 +77,7 @@ import {ConnectorError, ConnectorErrorType} from '@sailpoint/connector-sdk';
|
||||
* Thrown when an application missing configuration during initialization
|
||||
*/
|
||||
|
||||
export class InvalidConfigurationError extends ConnectorError {
|
||||
export class InvalidConfigurationException extends ConnectorError {
|
||||
/**
|
||||
* Constructor
|
||||
* @param message Error message
|
||||
@@ -64,7 +85,7 @@ export class InvalidConfigurationError extends ConnectorError {
|
||||
*/
|
||||
constructor(message: string, type?: ConnectorErrorType) {
|
||||
super(message, type);
|
||||
this.name = 'InvalidConfigurationError';
|
||||
this.name = 'InvalidConfigurationException';
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -74,7 +95,7 @@ Then throw the error in your code:
|
||||
[airtable.ts](https://github.com/sailpoint-oss/airtable-example-connector/blob/main/src/airtable.ts)
|
||||
|
||||
```javascript
|
||||
import { InvalidConfigurationError } from "./errors/invalid-configuration-error"
|
||||
import { InvalidConfigurationException } from "./errors/invalid-configuration-error"
|
||||
|
||||
export class AirtableClient {
|
||||
private readonly airTableBase: Airtable.Base
|
||||
@@ -82,10 +103,10 @@ export class AirtableClient {
|
||||
// Fetch necessary properties from config.
|
||||
// Following properties actually do not exist in the config -- it just serves as an example.
|
||||
if (config.apiKey == null) {
|
||||
throw new InvalidConfigurationError('token must be provided from config')
|
||||
throw new InvalidConfigurationException('token must be provided from config')
|
||||
}
|
||||
if (config.airtableBase == null) {
|
||||
throw new InvalidConfigurationError('airtableBase base id needed')
|
||||
throw new InvalidConfigurationException('airtableBase base id needed')
|
||||
}
|
||||
Airtable.configure({apiKey: config.apiKey})
|
||||
this.airTableBase = Airtable.base(config.airtableBase)
|
||||
@@ -95,3 +116,23 @@ export class AirtableClient {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Recommended custom exceptions and examples of when to use them
|
||||
|
||||
#### InvalidConfigurationException
|
||||
- Use this exception during any operation if the connector requires a certain configuration to connect to the managed-system, but the configuration is either faulty or not provided. This could happen before sending a request to the managed system.
|
||||
|
||||
#### InsufficientPermissionException
|
||||
- Use this exception during any operation if the connector gets a known managed system exception indicating a lack of permission.
|
||||
|
||||
#### InvalidRequestException
|
||||
- Use this exception during any operation if the connector is creating messages to be sent to the managed system but is failing to create a message. This could happen before sending a request to the managed system.
|
||||
|
||||
#### ObjectAlreadyExistsException
|
||||
- Use this exception during the provisioning operation of the type create(only) if the connector is trying to create an entity that already exists on the managed system.
|
||||
|
||||
#### InvalidResponseException
|
||||
- Use this exception during aggregation or in the getObject when the connector is unable to parse data received from managed system. This could happen if something fails when converting a managed system response to a ResourceObject.
|
||||
|
||||
#### TimeoutException
|
||||
- This is intended for cases in which the connector receives timeout related error/exceptions from the managed system.
|
||||
@@ -17,18 +17,31 @@ SaaS Connectivity is a cloud based connector runtime that makes developing and d
|
||||
|
||||
Connectors are the bridges between the SailPoint Identity Now (IDN) SaaS platform and the source systems that IDN needs to communicate with and aggregate data from. An example of a source system IDN may need to communicate with would be an Oracle HR system or GitHub. In these cases, IDN synchronizes data between systems to ensure account entitlements and state are correct through the organization.
|
||||
|
||||
## Why Are We Introducing a New Connector
|
||||
## Why We Are Introducing SaaS Connectivity
|
||||
|
||||
The primary driver for indroducing the SaaS Connectivity framework is to allow a way to connect to other cloud based sources in a truly SaaS architecture, without the need to rely on a VA. There are also other benefits that come with the SaaS Connectivity framework:
|
||||
- Ability to develop, debug and test custom connectors locally without any dependencies on IdentityNow
|
||||
- Features to customize the user interface when configuring the connector that are specific to the source
|
||||
- Support for more modern languages and frameworks
|
||||
|
||||
## Architecture of SaaS Connectivity
|
||||
|
||||
VA connectors always communicate with external sources through the Virtual Appliance (VA) as seen in the diagram below:
|
||||
|
||||

|
||||
|
||||
VA connectors can be disadvantageous because you need an on-prem virtual appliance to have any external connectivity with them, even when that connectivity is a SaaS service like Salesforce.com.
|
||||
|
||||
It is also challenging to create a custom connector in the VA Connector framework. Therefore, there are generic connectors available such as flat file, JDBC and webservice connectors. These options provide flexibility in configuring almost any source, but this configuration can be complex. For example, when you create a JDBC connector, you must use SQL to define the data model.
|
||||
|
||||
The new Cloud connectors work differently - they run on the IDN platform instead (see diagram below).
|
||||
The new Cloud connectors work differently - they run on the IDN platform instead:
|
||||
|
||||

|
||||
|
||||
With this process, you can run an entire IDN instance without a VA. The new connector also includes a CLI tool to manage cloud connectors and an SDK to create custom connectors. Because it is simpler to create a custom connector, you can create specific connectors for a variety of sources, and the connectors' configuration can be much simpler. For example, you can now configure a formerly complicated webservice connector by providing two parameters (Base URL and API Key) in a custom cloud connector.
|
||||
With both SaaS connectivity and traditional VA connectivity in place, you can have the best of both worlds. Below is a new diagram showing both of them working together to leverage both on-prem and cloud based sources.
|
||||
|
||||

|
||||
|
||||
## Connectivity Encryption
|
||||
|
||||
Any direct connectors that specify a virtual appliance (VA) use [Zero Knowledge Encryption](https://community.sailpoint.com/t5/Lighthouse/Protecting-Sensitive-Data-with-Zero-Knowledge-Encryption/ta-p/79657?attachment-id=452) schemes with an RSA 2048-bit asymmetric key pair: there is a private key on the VA for decryption and a public key in the cloud (as part of the VA cluster) for encryption.
|
||||
|
||||
SaaS connectors can't operate the same way because they don't communicate through VA clusters. Despite this, SaaS connectors can still leverage the asymmetric keypair scheme - the keystore simply resides in the cloud instead of on the VA. This keystore is not accessible by any API or source code, and there is regular rotation of those keypairs through SailPoint's DevOps-owned processes to ensure that security is maintained to SailPoint standards. Whenever you are storing secret data, use the ```secret``` or ```secrettextarea``` field types.
|
||||
|
||||
|
||||
|
||||
@@ -100,6 +100,13 @@ $ sail conn tags list -c example-connector
|
||||
+--------------------------------------+----------+----------------+
|
||||
```
|
||||
|
||||
:::caution Important
|
||||
|
||||
Make sure that you implement a form of version control or regular backup process for your connectors.
|
||||
You cannot recover the source code from IDN because it gets sent to IDN as a compiled and minified JavaScript (JS) bundle that cannot be easily expanded into its original source code structure.
|
||||
|
||||
:::
|
||||
|
||||
## Test Your Connector in IdentityNow
|
||||
|
||||
Follow these steps to test a connector bundle in both IdentityNow and the IdentityNow user interface (UI).
|
||||
|
||||
23
products/idn/docs/identity-now/saas-connectivity/videos.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: videos
|
||||
title: Videos
|
||||
pagination_label: Videos
|
||||
sidebar_label: Videos
|
||||
sidebar_position: 1.5
|
||||
sidebar_class_name: videos
|
||||
keywords: ['connectivity', 'connectors', 'videos']
|
||||
description: Helpful videos on using SaaS connectivity
|
||||
slug: /docs/saas-connectivity/videos
|
||||
tags: ['Connectivity']
|
||||
---
|
||||
import Video from '@site/src/components/Video';
|
||||
|
||||
## Videos
|
||||
|
||||
During our 2023 Developer Days Conference, we created several connectivity videos. These videos can help you as you start building connectors:
|
||||
|
||||
- [Roadmap and Introduction](https://www.youtube.com/watch?v=6FGkKj6aKko)
|
||||
- [Building a Complete Connector Walkthrough](https://www.youtube.com/watch?v=KB1jdE09lE4)
|
||||
- [SDKs in practice](https://www.youtube.com/watch?v=UWeokOXuAuk)
|
||||
|
||||
<Video source="https://www.youtube.com/embed/KB1jdE09lE4"></Video>
|
||||
@@ -26,7 +26,7 @@ For an initial (temporary) password, set a static value driven off a formula tha
|
||||
- The user's two-digit start month comes next (from the user's hire date).
|
||||
- The last part of the password is a static string: "RstP\*!7".
|
||||
|
||||
## Create the Example Source from a Deliminated file
|
||||
## Create the Example Source from a delimited file
|
||||
|
||||
This is the CSV file you will upload to create your source for testing this transform:
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ In this guide, you will learn how to use [IdentityNow's Transform REST APIs](/id
|
||||
|
||||
To call the APIs for transforms, you need a personal access token and your tenant's name to provide with the request. For more information about how to get a personal access token, see [Personal Access Tokens](../../../../api/authentication.md#personal-access-tokens). For more information about how to get the name of your tenant, see [Finding Your Organization Tenant Name](../../../../api/getting-started.md#finding-your-orgtenant-name).
|
||||
|
||||
Before you create your first custom transform, see what transforms are already in the tenant. You can get this information by calling the [List Transforms API](/idn/api/v3/get-transforms-list).
|
||||
Before you create your first custom transform, see what transforms are already in the tenant. You can get this information by calling the [List Transforms API](/idn/api/v3/list-transforms).
|
||||
|
||||
```bash
|
||||
curl --location --request GET 'https://{tenant}.api.identitynow.com/v3/transforms' --header 'Authorization: Bearer {token}'
|
||||
|
||||
@@ -19,9 +19,9 @@ This transform leverages the Java SimpleDateFormat syntax; see the [References](
|
||||
:::note Other Considerations
|
||||
|
||||
- In addition to explicit SimpleDateFormat syntax, the date format transform also recognizes several built-in "named" constructs:
|
||||
- **ISO8601:** This is the date format corresponding to the ISO8601 standard. The exact format is expressed as yyyy-MM-dd'T'HH:mm:ss.SSSX.
|
||||
- **LDAP:** This is the date format corresponding to the LDAP date format standard, also expressed as yyyyMMddHHmmss.Z.
|
||||
- **PEOPLE_SOFT:** This is the date format format used by People Soft, also expressed as MM/dd/yyyy.
|
||||
- **ISO8601:** This is the date format corresponding to the ISO8601 standard. The exact format is expressed as "yyyy-MM-dd'T'HH:mm:ss.SSSZ".
|
||||
- **LDAP:** This is the date format corresponding to the LDAP date format standard, also expressed as "yyyyMMddHHmmss.Z".
|
||||
- **PEOPLE_SOFT:** This is the date format format used by People Soft, also expressed as "MM/dd/yyyy".
|
||||
- **EPOCH_TIME_JAVA:** This represents the incoming date value as the elapsed time in milliseconds from midnight, January 1st, 1970.
|
||||
- **EPOCH_TIME_WIN32:** This represents the incoming date value as the elapsed time in 100-nanosecond intervals from midnight, January 1st, 1601.
|
||||
|
||||
@@ -29,7 +29,7 @@ This transform leverages the Java SimpleDateFormat syntax; see the [References](
|
||||
|
||||
## Transform Structure
|
||||
|
||||
The date format transform takes whatever value provided as the input, parses the datetime based on the `inputFormat` provided, and then reformats it into the desired `outputFormat`.
|
||||
The date format transform takes whatever value provided as the input, parses the datetime based on the `inputFormat` provided, and then reformats it into the desired `outputFormat`.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -53,7 +53,11 @@ The date format transform takes whatever value provided as the input, parses the
|
||||
- If no inputFormat is provided, the transform assumes that it is in [ISO8601 format](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
- **outputFormat** - This string value indicates either the explicit SimpleDateFormat or the built-in named format that the data is formatted into.
|
||||
- If no outputFormat is provided, the transform assumes that it is in [ISO8601 format](https://en.wikipedia.org/wiki/ISO_8601).
|
||||
- **input** - This is an optional attribute that can explicitly define the input data passed into the transform logic. If no input is provided, the transform takes its input from the source and attribute combination configured with the UI.
|
||||
- **input** - This is an optional attribute that can explicitly define the input data passed into the transform logic. If no input is provided, the transform takes its input from the source and attribute combination configured with the UI.
|
||||
|
||||
:::note Important
|
||||
This transform does not currently support the "now" keyword as an input value.
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -14,19 +14,19 @@ tags: ['Transforms', 'Transform Operations']
|
||||
|
||||
Use the date math transform to add, subtract, and round components of a timestamp's incoming value. It also allows you to work with a referential value of "now" to run operations against the current date and time instead of a fixed value.
|
||||
|
||||
The output format for the DateMath transform is "yyyy-MM-dd'T'HH:mm." When you use this transform inside another transform (e.g., [dateCompare](./date-compare.md)), make sure to convert to [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) first.
|
||||
The output format for the DateMath transform is "yyyy-MM-dd'T'HH:mm". When you use this transform inside another transform (e.g., [dateCompare](./date-compare.md)), make sure to convert to [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) first.ADFJKL|:
|
||||
|
||||
|
||||
:::note Other Considerations
|
||||
|
||||
- The input datetime value must always be in [ISO8601 format](https://en.wikipedia.org/wiki/ISO_8601), in UTC time zone:
|
||||
|
||||
- yyyy-MM-ddThh:mm:ss:nnnZ
|
||||
- 2020-10-28T12:00:00.000Z, as an example
|
||||
- Use this format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", i.e., "2020-10-28T12:00:00.000Z".
|
||||
- The dateFormat transform can help get data into this format.
|
||||
|
||||
- The industry standard for rounding is actually date/time truncation. When rounding down, the fractional value is truncated from the incoming data. When rounding up, the fractional value is truncated and the next unit of time is added. Refer to the Transform Structure section below for examples.
|
||||
- When you are rounding, the "week" unit of time is not supported as a metric, and attempting to round up or down a week will result in an error.
|
||||
- If you are using the "now" keyword and an input date is also applied as the implicitly or explicitly definted input parameter, the transform prefers using "now" and ignores the data in the `input` attribute.
|
||||
- The industry standard for rounding is actually date/time truncation. When the transform is rounding down, it truncates the the fractional value from the incoming data. When the transform is rounding up, it truncates the fractional value and adds the next unit of time. For examples, refer to the [Transform Structure](#transform-structure).
|
||||
- The "week" unit of time is not supported as a metric when you are rounding. Rounding up or down a week will result in an error.
|
||||
- If you are using the "now" keyword and you have also applied an input date as the implicitly or explicitly definted input parameter, the transform prefers using "now" and ignores the data in the `input` attribute.
|
||||
|
||||
:::
|
||||
|
||||
@@ -79,7 +79,7 @@ Some examples of expressions are:
|
||||
|
||||
- **Required Attributes**
|
||||
|
||||
- **type** - This must always be set to `dateMath.`
|
||||
- **type** - This must always be set to `dateMath`.
|
||||
- **name** - This is a required attribute for all transforms. It represents the name of the transform as it will appear in the UI's dropdown menus.
|
||||
- **expression** - A string value of the date and time components to operate on, along with the math operations to execute. Multiple operations on multiple components are supported.
|
||||
|
||||
@@ -146,7 +146,7 @@ This transform takes the `startDate` attribute from a user's record in the "HR S
|
||||
|
||||
<p> </p>
|
||||
|
||||
This transform take the `HIREDATE` from Workday and converts it to [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) to be used in the Date Math transform. The Date Math transform then creates a new Date of `HIREDATE + 1`. Since that is then outputted in the format "yyyy-MM-dd'T'HH:mm," you can then use it in a [dateFormat](/idn/docs/transforms/operations/date-format) transform to give a WIN32 formatted date.
|
||||
This transform take the `HIREDATE` from Workday and converts it to [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) to be used in the Date Math transform. The Date Math transform then creates a new Date of `HIREDATE + 1`. Since that is then outputted in the format "yyyy-MM-dd'T'HH:mm", you can then use it in a [dateFormat](/idn/docs/transforms/operations/date-format) transform to give a WIN32 formatted date.
|
||||
|
||||
**Transform Request Body**:
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ The following are examples of diacritical marks:
|
||||
> - Ň
|
||||
> - Ŵ
|
||||
|
||||
The decomposeDiacriticalMarks transform uses the [Normalizer library](https://docs.oracle.com/javase/7/docs/api/java/text/Normalizer.html) to decompose the diacritical marks. It specifically uses the Normalization Form KD (NFKD), as described in Sections 3.6, 3.10, and 3.11 of the Unicode Standard, also summarized under [Annex 4: Decomposition](https://www.unicode.org/reports/tr15/tr15-23.html#Decomposition).
|
||||
|
||||
After decomposition, the transform uses a [Regex Replace](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html) to replace all diacritical marks by using the `InCombiningDiacriticalMarks` property of Unicode (ex. `replaceAll("[\\p{InCombiningDiacriticalMarks}]", "")`).
|
||||
|
||||
## Transform Structure
|
||||
|
||||
The transform for decompose diacritical marks requires only the transform's `type` and `name` attributes:
|
||||
@@ -88,3 +92,18 @@ Output: "Dubcek"
|
||||
"name": "Decompose Diacritical Marks Transform"
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To run some tests in code, use this java code to compare the results of what the transform does to what your code does:
|
||||
|
||||
```java
|
||||
import java.text.Normalizer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// Decomposes characters from their diacritical marks
|
||||
input = Normalizer.normalize(input, Normalizer.Form.NFKD);
|
||||
|
||||
// Removes the marks
|
||||
input = input.replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
|
||||
```
|
||||
|
||||
@@ -43,7 +43,7 @@ In addition to the standard `type` and `name` attributes, the split transform re
|
||||
- **throws** - This boolean (true/false) value indicates whether an exception is thrown and returned as an output when an index is out of bounds with the resulting array (i.e., the provided `index` value is larger than the size of the array).
|
||||
- true - The transform returns "IndexOutOfBoundsException".
|
||||
- false - The transform returns null.
|
||||
- If no throws value is provided, the transform default to false and returns a null.
|
||||
- If no throws value is provided, the transform defaults to true and returns an "IndexOutOfBoundsException".
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ const sidebars = {
|
||||
type: 'doc',
|
||||
id: 'api/rate-limit',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'api/postman-collections',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'V3 APIs',
|
||||
@@ -60,5 +64,11 @@ const sidebars = {
|
||||
dirName: 'docs',
|
||||
},
|
||||
],
|
||||
sdkSidebar: [
|
||||
{
|
||||
type: 'autogenerated',
|
||||
dirName: 'tools',
|
||||
},
|
||||
],
|
||||
};
|
||||
module.exports = sidebars;
|
||||
|
||||
3
products/idn/tools/cli/_category_.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"collapsible": false
|
||||
}
|
||||