mirror of
https://github.com/LukeHagar/developer.sailpoint.com.git
synced 2025-12-06 04:19:31 +00:00
Merge main
This commit is contained in:
@@ -70,7 +70,7 @@ module.exports = {
|
||||
{
|
||||
position: 'left',
|
||||
label: 'Blog',
|
||||
to: 'https://medium.com/sailpointengineering',
|
||||
to: '/blog',
|
||||
},
|
||||
{
|
||||
position: 'left',
|
||||
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -19,7 +19,10 @@
|
||||
"docusaurus-theme-openapi-docs": "^1.5.1",
|
||||
"prism-react-renderer": "^1.3.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-tabs": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/core": "2.2.0",
|
||||
@@ -11024,8 +11027,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "8.0.5",
|
||||
"license": "MIT",
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz",
|
||||
"integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==",
|
||||
"dependencies": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"@types/prop-types": "^15.0.0",
|
||||
@@ -11302,6 +11306,27 @@
|
||||
"react": ">=15"
|
||||
}
|
||||
},
|
||||
"node_modules/react-spinners": {
|
||||
"version": "0.13.8",
|
||||
"resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz",
|
||||
"integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-tabs": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-4.3.0.tgz",
|
||||
"integrity": "sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q==",
|
||||
"dependencies": {
|
||||
"clsx": "^1.1.0",
|
||||
"prop-types": "^15.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-textarea-autosize": {
|
||||
"version": "8.4.0",
|
||||
"dev": true,
|
||||
@@ -21693,7 +21718,9 @@
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"react-markdown": {
|
||||
"version": "8.0.5",
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz",
|
||||
"integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"@types/prop-types": "^15.0.0",
|
||||
@@ -21858,6 +21885,21 @@
|
||||
"tiny-warning": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"react-spinners": {
|
||||
"version": "0.13.8",
|
||||
"resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.8.tgz",
|
||||
"integrity": "sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-tabs": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-4.3.0.tgz",
|
||||
"integrity": "sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q==",
|
||||
"requires": {
|
||||
"clsx": "^1.1.0",
|
||||
"prop-types": "^15.5.0"
|
||||
}
|
||||
},
|
||||
"react-textarea-autosize": {
|
||||
"version": "8.4.0",
|
||||
"dev": true,
|
||||
|
||||
@@ -32,7 +32,10 @@
|
||||
"docusaurus-theme-openapi-docs": "^1.5.1",
|
||||
"prism-react-renderer": "^1.3.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-tabs": "^4.3.0"
|
||||
},
|
||||
"overrides": {
|
||||
"mermaid": "9.1.7"
|
||||
|
||||
@@ -153,7 +153,7 @@ This document covers these three common flows:
|
||||
3. [**Refresh Token**](https://oauth.net/2/grant-types/refresh-token/) - Clients use this grant type to exchange a refresh token for a new `access_token` when the existing `access_token` has expired. This allows clients to continue using the APIs without having to re-authenticate as frequently. This grant type is commonly used together with `Authorization Code` to prevent a user from having to log in several times per day.
|
||||
|
||||
One way to determine which authorization flow you need to use is to look at the specification for the endpoint you want to use.
|
||||
The endpoint will haev the supported OAuth flows listed under the 'Authorization' dropdown, like the [List Access Profiles endpoint](https://developer.sailpoint.com/idn/api/beta/list-access-profiles):
|
||||
The endpoint will have the supported OAuth flows listed under the 'Authorization' dropdown, like the [List Access Profiles endpoint](https://developer.sailpoint.com/idn/api/beta/list-access-profiles):
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -36,7 +36,10 @@ If there are no changes, you're up to date. If there are changes, the screen lis
|
||||
|
||||
## 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.
|
||||
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 import the environment into your workspace, select 'Run in Postman'.
|
||||
|
||||
| Environment | [](https://www.postman.com/sailpoint/workspace/identitynow/environment/23226990-ed571d4f-37a3-4a2c-9105-5d8d8cce1d20/fork) |
|
||||
|------|----------------------------|
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ if( startDate.before( today ) && endDate.after( today ) ) {
|
||||
return "inactive";
|
||||
}
|
||||
|
||||
// If we haven't calculated a state already; return null.
|
||||
// If we haven't calculated a state already, return null.
|
||||
return null;
|
||||
|
||||
]]></Source>
|
||||
|
||||
@@ -15,6 +15,12 @@ tags: ['Rules']
|
||||
|
||||
In SailPoint solutions, rules serve as a flexible configuration framework implementers can leverage to preform complex or advanced configurations. Though rules allow some advanced flexibility, you must take special considerations when you are deciding to implement rules.
|
||||
|
||||
## Java Beanshell
|
||||
|
||||
Rules in IdentityNow are written in Java Beanshell, a lightweight scripting language that allows you to define custom logic and behavior within the rules. Java Beanshell provides a familiar syntax similar to Java, making it easier for implementers to create and maintain rule configurations.
|
||||
|
||||
For more information about Java Beanshell, you can refer to the [Java Beanshell Documentation](https://github.com/beanshell/beanshell/wiki).
|
||||
|
||||
## Rule Execution
|
||||
|
||||
IdentityNow (IDN) is a multi-tenant cloud solution, and its architecture varies differently from other SailPoint products like IdentityIQ (IIQ). Therefore, the way rules execute within IDN reflects the architectural design considerations the platform was built on. These considerations determine the rule's limitations.
|
||||
|
||||
@@ -11,9 +11,16 @@ tags: ['Connectivity', 'Connector Command']
|
||||
|
||||
| Input/Output | Data Type |
|
||||
| :----------- | :------------------: |
|
||||
| Input | undefined |
|
||||
| Input | StdAccountListInput |
|
||||
| Output | StdAccountListOutput |
|
||||
|
||||
### Example StdAccountListInput
|
||||
|
||||
```javascript
|
||||
"state": {"date": "1686341338871"},
|
||||
"stateful": true
|
||||
```
|
||||
|
||||
### Example StdAccountListOutput
|
||||
|
||||
```javascript
|
||||
@@ -70,6 +77,12 @@ IDN will throw a connection timeout error if your connector doesn't respond with
|
||||
|
||||
:::
|
||||
|
||||
:::caution Important
|
||||
|
||||
IDN supports [delta aggregation](#delta-aggregation-state). If your source has a large number of accounts that will be syncronized with IDN, then it is highly recommended to utilize [delta aggregation](#delta-aggregation-state) for the source.
|
||||
|
||||
:::
|
||||
|
||||
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
|
||||
@@ -162,3 +175,60 @@ The result of the account list command is not an array of objects but several in
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Delta Aggregation (State)
|
||||
|
||||
If your source can keep track of changes to the data in some way, then delta aggregation can be performed on a source. In order to implement, there are a few things that need to be configured
|
||||
|
||||
1. In your connector-spec.json file, the feature needs to be enabled by adding the following key: ```"supportsStatefulCommands": true,``` and in the sourceConfig section, a checkbox needs to be added to enable state with the key ```spConnEnableStatefulCommands```:
|
||||
|
||||
```javascript
|
||||
"supportsStatefulCommands": true,
|
||||
...
|
||||
{
|
||||
"key": "spConnEnableStatefulCommands",
|
||||
"label": "Stateful",
|
||||
"required": true,
|
||||
"type": "checkbox"
|
||||
}
|
||||
```
|
||||
|
||||
2. In the ```stdAccountList``` command, when you are done sending accounts, you need to also send the state to IDN so it knows where to start the next time it sends a list request:
|
||||
|
||||
```javascript
|
||||
const state = {"data": Date.now().toString()}
|
||||
...
|
||||
res.saveState(state)
|
||||
```
|
||||
|
||||
In the above example, I am capturing the date, but you can use any value you want to store the state
|
||||
|
||||
:::caution Important
|
||||
|
||||
The state that you send using the ```saveState``` command MUST be a json object, and it is recommend to only save strings to ensure proper serialization/deserialization of the data. You cannot send a simple string or number or it will not properly save the state.
|
||||
|
||||
:::
|
||||
|
||||
3. In the ```stdAccountList``` command, you need to properly handle the state object. Something like below checks the stateful boolean as well as the state object and fetches accounts accordingly:
|
||||
|
||||
```javascript
|
||||
.stdAccountList(async (context: Context, input: StdAccountListInput, res: Response<StdAccountListOutput>) => {
|
||||
let accounts = []
|
||||
const state = {"data": Date.now().toString()}
|
||||
if (!input.state && input.stateful) {
|
||||
logger.info(input, "No state provided, fetching all accounts")
|
||||
accounts = await airtable.getAllAccounts()
|
||||
} else if (input.state && input.stateful) {
|
||||
logger.info(input ,"Current state provided, only fetching accounts after that state")
|
||||
accounts = await airtable.getAllStatefulAccounts(new Date(Number(input.state?.data)))
|
||||
} else {
|
||||
logger.info(input.state ,"Source is not stateful, getting all acounts")
|
||||
accounts = await airtable.getAllAccounts()
|
||||
}
|
||||
logger.info(accounts, "fetched the following accounts from Airtable")
|
||||
for (const account of accounts) {
|
||||
res.send(account.toStdAccountListOutput())
|
||||
}
|
||||
res.saveState(state)
|
||||
})
|
||||
```
|
||||
@@ -108,4 +108,67 @@ private buildStandardObject(): StdEntitlementReadOutput | StdEntitlementListOutp
|
||||
|
||||
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).
|
||||
|
||||
:::
|
||||
:::
|
||||
|
||||
:::caution Important
|
||||
|
||||
IDN supports [delta aggregation](#delta-aggregation-state). If your source has a large number of entitlements that will be syncronized with IDN, then it is highly recommended to utilize [delta aggregation](#delta-aggregation-state) for the source.
|
||||
|
||||
:::
|
||||
|
||||
## Delta Aggregation (State)
|
||||
|
||||
If your source can keep track of changes to the data in some way, then delta aggregation can be performed on a source. In order to implement, there are a few things that need to be configured
|
||||
|
||||
1. In your connector-spec.json file, the feature needs to be enabled by adding the following key: ```"supportsStatefulCommands": true,``` and in the sourceConfig section, a checkbox needs to be added to enable state with the key ```spConnEnableStatefulCommands```:
|
||||
|
||||
```javascript
|
||||
"supportsStatefulCommands": true,
|
||||
...
|
||||
{
|
||||
"key": "spConnEnableStatefulCommands",
|
||||
"label": "Stateful",
|
||||
"required": true,
|
||||
"type": "checkbox"
|
||||
}
|
||||
```
|
||||
|
||||
2. In the ```stdEntitlementList``` command, when you are done sending entitlments, you need to also send the state to IDN so it knows where to start the next time it sends a list request:
|
||||
|
||||
```javascript
|
||||
const state = {"data": Date.now().toString()}
|
||||
...
|
||||
res.saveState(state)
|
||||
```
|
||||
|
||||
In the above example, I am capturing the date, but you can use any value you want to store the state
|
||||
|
||||
:::caution Important
|
||||
|
||||
The state that you send using the ```saveState``` command MUST be a json object, and it is recommend to only save strings to ensure proper serialization/deserialization of the data. You cannot send a simple string or number or it will not properly save the state.
|
||||
|
||||
:::
|
||||
|
||||
3. In the ```stdEntitlementList``` command, you need to properly handle the state object. Something like below checks the stateful boolean as well as the state object and fetches accounts accordingly:
|
||||
|
||||
```javascript
|
||||
.stdEntitlementList(async (context: Context, input: StdEntitlementListInput, res: Response<StdEntitlementListOutput>) => {
|
||||
let groups = []
|
||||
const state = {"data": Date.now().toString()}
|
||||
if (!input.state && input.stateful) {
|
||||
logger.info(input, "No state provided, fetching all entitlements")
|
||||
const groups = await airtable.getAllEntitlements()
|
||||
} else if (input.state && input.stateful) {
|
||||
logger.info(input ,"Current state provided, only fetching entitlements after that state")
|
||||
const groups = await airtable.getAllStatefulEntitlements(new Date(Number(input.state?.data)))
|
||||
} else {
|
||||
logger.info(input.state ,"Source is not stateful, getting all entitlements")
|
||||
const groups = await airtable.getAllEntitlements()
|
||||
}
|
||||
logger.info(groups, "fetched the following entitlements in Airtable")
|
||||
for (const group of groups) {
|
||||
res.send(group.toStdEntitlementListOutput())
|
||||
}
|
||||
res.saveState(state)
|
||||
})
|
||||
```
|
||||
@@ -29,7 +29,7 @@ The following describes in detail the different fields in the connector spec:
|
||||
|
||||
- For example, the stdAccountRead command input is the StdAccountReadInput. if you select keyType as “simple,” then the StdAccountReadInput.key will be the type SimpleKey.
|
||||
|
||||
- **commands:** The list of commands the connector supports. A full list of available commands can be found here.
|
||||
- **commands:** The list of commands the connector supports. A full list of available commands can be found [here](../connector-commands/index.md).
|
||||
|
||||
- **sourceConfig** A list of configuration items you must provide when you create a source in IDN. The order of these items is preserved in the UI.
|
||||
- **type:** This is always “menu” - it indicates a new menu for the sidebar. You can have multiple sections defined for complex connector configurations
|
||||
|
||||
@@ -20,7 +20,7 @@ To build the CLI, the following packages are required:
|
||||
|
||||
To develop a connector, the following packages are required:
|
||||
|
||||
- Node >= 14.17.3
|
||||
- Node >= 16.2.0
|
||||
|
||||
## IDE
|
||||
|
||||
|
||||
@@ -156,8 +156,8 @@ The following variables are available to the Apache Velocity template engine whe
|
||||
| Variable | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| identity | sailpoint.object.Identity | This is the identity the attribute promotion is performed on. |
|
||||
| oldValue | Object | This is the definition of the attribute being promoted. |
|
||||
| attributeDefinition | sailpoint.object.ObjectAttribute | This is the attribute's previous value. |
|
||||
| attributeDefinition | sailpoint.object.ObjectAttribute | This is the definition of the attribute being promoted. |
|
||||
| oldValue | Object | This is the attribute's previous value. |
|
||||
|
||||
### Account Profile Context
|
||||
|
||||
|
||||
19
src/components/ambassador/AmbassadorBanner/index.js
Normal file
19
src/components/ambassador/AmbassadorBanner/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
export default function AmbassadorBanner() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
<div className={styles.imageContainer}>
|
||||
<img className={styles.headerImage} src={useBaseUrl('/ambassador/ambassador_banner_template.png')}></img>
|
||||
<div className={styles.ambassadorHeaderText}>
|
||||
Ambassadors
|
||||
</div>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/components/ambassador/AmbassadorBanner/styles.module.css
Normal file
28
src/components/ambassador/AmbassadorBanner/styles.module.css
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
.ambassadorHeaderText {
|
||||
position: relative;
|
||||
color: #ffffff;
|
||||
font-size: 48px;
|
||||
max-width: 396px;
|
||||
font-weight: bold;
|
||||
line-height: 133%;
|
||||
top: -84px;
|
||||
left: 92px;
|
||||
}
|
||||
|
||||
.background {
|
||||
width: 100%;
|
||||
object-fit: repeat;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
background: rgb(0,51,161);
|
||||
background: linear-gradient(90deg, rgba(0,51,161,1) 0%, rgba(84,192,232,1) 100%);
|
||||
}
|
||||
|
||||
.headerImage {
|
||||
height: 90px;
|
||||
}
|
||||
38
src/components/ambassador/AmbassadorCard/index.js
Normal file
38
src/components/ambassador/AmbassadorCard/index.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
export default function AmbassadorCard({
|
||||
data
|
||||
}) {
|
||||
|
||||
return (
|
||||
<Link to={data.link} className={styles.link}>
|
||||
|
||||
|
||||
<div className={styles.card}>
|
||||
<div className={styles.cardFaceContainer}>
|
||||
<img className={styles.cardFace} src={useBaseUrl(data.creatorImage)}></img>
|
||||
<div className={styles.cardNameContainer}>
|
||||
<div className={styles.name}>{data.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.bio} dangerouslySetInnerHTML={{__html: data.bio}}></div>
|
||||
|
||||
<div className={styles.cardData}>
|
||||
<img className={styles.cardEye} src={useBaseUrl('/icons/square-check-regular.svg')}></img>
|
||||
<div className={styles.cardCommentText}>{data.answers} solutions</div>
|
||||
</div>
|
||||
<div className={styles.cardDataLower}>
|
||||
<img className={styles.cardEye} src={useBaseUrl('/icons/calendar-clock-light.svg')}></img>
|
||||
<div className={styles.cardCommentText}>member since {data.member_since}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
136
src/components/ambassador/AmbassadorCard/styles.module.css
Normal file
136
src/components/ambassador/AmbassadorCard/styles.module.css
Normal file
@@ -0,0 +1,136 @@
|
||||
/* Getting Started Card */
|
||||
|
||||
.link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
height: 500px;
|
||||
max-width: 420px;
|
||||
/* UI Properties */
|
||||
background: var(--dev-card-background);
|
||||
box-shadow: var(--dev-card-shadow);
|
||||
border: 1px solid var(--dev-card-background);
|
||||
border-radius: 40px;
|
||||
opacity: 1;
|
||||
transition: all 0.3s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
cursor: pointer;
|
||||
transform: translate(0px, -5px);
|
||||
box-shadow: var(--dev-card-selected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.cardFaceContainer {
|
||||
display: flex;
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
.cardFace {
|
||||
border-radius: 99px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.cardNameContainer {
|
||||
margin-left: 15px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 25px;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
top: 124px;
|
||||
left: 0;
|
||||
color: var(--dev-text-color-normal);
|
||||
}
|
||||
|
||||
.titleText {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
width: 100%;
|
||||
letter-spacing: 2px;
|
||||
top: 150px;
|
||||
left: 0;
|
||||
color: var(--dev-text-color-normal);
|
||||
}
|
||||
|
||||
.location {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
letter-spacing: 2px;
|
||||
top: 141px;
|
||||
left: 0;
|
||||
color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
.cardArrow {
|
||||
position: absolute;
|
||||
margin: 20px;
|
||||
bottom: 5px;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.bio {
|
||||
position: absolute;
|
||||
margin: 30px auto;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
color: var(--ifm-color-primary);
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
|
||||
.cardData {
|
||||
position: absolute;
|
||||
bottom: 50px;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cardEye {
|
||||
fill: #96a9bb;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
|
||||
margin-left: 5px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.cardCommentText {
|
||||
color: #96a9ba;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
margin-left: 5px;
|
||||
margin-bottom: 5px;
|
||||
height: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.cardDataLower {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
109
src/components/ambassador/AmbassadorCards/index.js
Normal file
109
src/components/ambassador/AmbassadorCards/index.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import AmbassadorCard from '../AmbassadorCard';
|
||||
import BounceLoader from 'react-spinners/BounceLoader';
|
||||
|
||||
import {getAmbassadors, getAmbassadorDetails} from '../../../services/DiscourseService';
|
||||
export default function AmbassadorCards({
|
||||
expert
|
||||
}) {
|
||||
const [cardData, setCardData] = React.useState();
|
||||
const [loadingCards, setLoadingCards] = React.useState(true);
|
||||
|
||||
const getPosts = async () => {
|
||||
const data = await getAmbassadors(expert);
|
||||
|
||||
const resultset = []
|
||||
if (data.members) {
|
||||
const memberDetails = await getAmbassadorDetails(data.members.map(item => item.id))
|
||||
for (const member of data.members) {
|
||||
const memberDetail = memberDetails.users.filter(item => item.id === member.id)[0]
|
||||
if (member.avatar_template.includes("developer.sailpoint.com") && memberDetail.bio_excerpt && memberDetail.bio_excerpt.length > 60 && memberDetail.accepted_answers > 0) {
|
||||
resultset.push(await getMemberList(member, memberDetail))
|
||||
}
|
||||
}
|
||||
resultset.sort((a, b) => a.date - b.date)
|
||||
setCardData(resultset);
|
||||
|
||||
} else {
|
||||
setCardData(undefined);
|
||||
}
|
||||
setLoadingCards(false);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
getPosts();
|
||||
setCardData(undefined);
|
||||
setLoadingCards(true);
|
||||
}, []);
|
||||
|
||||
if (cardData) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<div className={styles.gridContainer}>
|
||||
{cardData.map(function(a, index){
|
||||
return <AmbassadorCard
|
||||
key={a.link}
|
||||
data={a}
|
||||
></AmbassadorCard>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (loadingCards) {
|
||||
return (
|
||||
<BounceLoader
|
||||
className={styles.spinnerCenter}
|
||||
color={'#0033a1'}
|
||||
loading={true}
|
||||
size={150}
|
||||
aria-label="Loading Spinner"
|
||||
data-testid="loader"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.noFound}>
|
||||
{' '}
|
||||
No Ambassadors Found with the Given Search Criteria
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getMemberList(member, details) {
|
||||
|
||||
return {
|
||||
name: member.name,
|
||||
creatorImage: getavatarURL(member.avatar_template),
|
||||
title: member.title,
|
||||
bio: details.bio_excerpt,
|
||||
member_since: new Date(member.added_at).toLocaleString('default', {month: 'long'}) + ' ' + new Date(member.added_at).toISOString().slice(0, 4),
|
||||
badge_count: details.badge_count,
|
||||
answers: details.accepted_answers,
|
||||
location: details.location,
|
||||
website: details.website_name,
|
||||
link:
|
||||
'https://developer.sailpoint.com/discuss/u/' +
|
||||
member.username +
|
||||
'/summary',
|
||||
};
|
||||
}
|
||||
|
||||
function getavatarURL(avatar) {
|
||||
if (avatar.includes("developer.sailpoint.com")) {
|
||||
return "https://developer.sailpoint.com" + avatar.replace("{size}", "120")
|
||||
} else {
|
||||
return avatar.replace("{size}", "120")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function styleExcerpt(excerpt) {
|
||||
if (excerpt.length > 150) {
|
||||
return excerpt.slice(0, 150) + "..."
|
||||
} else {
|
||||
return excerpt.replace("…", "")
|
||||
}
|
||||
}
|
||||
|
||||
35
src/components/ambassador/AmbassadorCards/styles.module.css
Normal file
35
src/components/ambassador/AmbassadorCards/styles.module.css
Normal file
@@ -0,0 +1,35 @@
|
||||
/* Getting Started Card container */
|
||||
.gridContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
grid-gap: 40px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 0px auto;
|
||||
max-width: 1300px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.space {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.noFound {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
padding: 8px;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.spinnerCenter {
|
||||
margin: 100px auto;
|
||||
max-width: 1300px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
19
src/components/blog/BlogBanner/index.js
Normal file
19
src/components/blog/BlogBanner/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
export default function BlogBanner() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
<div className={styles.imageContainer}>
|
||||
<img className={styles.headerImage} src={useBaseUrl('/blog/blog_banner_template.png')}></img>
|
||||
<div className={styles.blogHeaderText}>
|
||||
Blog
|
||||
</div>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/components/blog/BlogBanner/styles.module.css
Normal file
28
src/components/blog/BlogBanner/styles.module.css
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
.blogHeaderText {
|
||||
position: relative;
|
||||
color: #ffffff;
|
||||
font-size: 48px;
|
||||
max-width: 396px;
|
||||
font-weight: bold;
|
||||
line-height: 133%;
|
||||
top: -84px;
|
||||
left: 59px;
|
||||
}
|
||||
|
||||
.background {
|
||||
width: 100%;
|
||||
object-fit: repeat;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
background: rgb(0,51,161);
|
||||
background: linear-gradient(90deg, rgba(0,51,161,1) 0%, rgba(84,192,232,1) 100%);
|
||||
}
|
||||
|
||||
.headerImage {
|
||||
height: 90px;
|
||||
}
|
||||
59
src/components/blog/BlogCard/index.js
Normal file
59
src/components/blog/BlogCard/index.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
export default function BlogCard({
|
||||
link,
|
||||
title,
|
||||
tags,
|
||||
creatorImage,
|
||||
image,
|
||||
excerpt,
|
||||
name,
|
||||
views,
|
||||
replies,
|
||||
readTime
|
||||
}) {
|
||||
|
||||
return (
|
||||
<Link to={link}>
|
||||
<div className={styles.card} >
|
||||
|
||||
<div className={styles.cardData}>
|
||||
<img className={styles.cardEye} src={useBaseUrl('/blog/eye-regular.svg')}></img>
|
||||
<div className={styles.cardCommentText}>{views}</div>
|
||||
<img className={styles.cardComment} src={useBaseUrl('/blog/comment-light.svg')}></img>
|
||||
<div className={styles.cardCommentText}>{replies}</div>
|
||||
<img className={styles.cardComment} src={useBaseUrl('/blog/clock-light.svg')}></img>
|
||||
<div className={styles.cardCommentText}>{readTime} minute read</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.cardUser}>
|
||||
<img className={styles.cardFace} src={useBaseUrl(creatorImage)}></img>
|
||||
<div className={styles.cardName}>{name}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className={styles.cardText}>
|
||||
<img className={styles.cardImage} src={useBaseUrl(image)}></img>
|
||||
<div className={styles.cardTitle}>{title}</div>
|
||||
<div className={styles.tags}>
|
||||
{tags?.map((tag, index) => {
|
||||
if (index > 2) {
|
||||
return '';
|
||||
}
|
||||
return <div key={tag} className={styles.tag}>{tag}</div>;
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.cardBody}>{excerpt}</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
127
src/components/blog/BlogCard/styles.module.css
Normal file
127
src/components/blog/BlogCard/styles.module.css
Normal file
@@ -0,0 +1,127 @@
|
||||
/* Getting Started Card */
|
||||
.card {
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
height: 650px;
|
||||
/* UI Properties */
|
||||
background: var(--dev-card-background);
|
||||
box-shadow: var(--dev-card-shadow);
|
||||
border: 1px solid var(--dev-card-background);
|
||||
border-radius: 40px;
|
||||
opacity: 1;
|
||||
transition: all 0.3s;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
cursor: pointer;
|
||||
transform: translate(0px, -5px);
|
||||
box-shadow: var(--dev-card-selected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.cardText {
|
||||
position: absolute;
|
||||
margin: 22px;
|
||||
min-width: 170px;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
|
||||
.cardTitle {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
background-color: var(--dev-tag-highlight);
|
||||
padding: 0px 8px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.cardUser {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 25px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cardFace {
|
||||
border-radius: 9999px;
|
||||
margin: auto;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.cardName {
|
||||
min-width: 170px;
|
||||
margin-top: 7px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.cardData {
|
||||
position: absolute;
|
||||
bottom: 50px;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cardEye {
|
||||
fill: #96a9bb;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
|
||||
margin-left: 5px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.cardComment {
|
||||
margin-left: 10px;
|
||||
fill: #96a9bb;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.cardCommentText {
|
||||
color: #96a9ba;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
margin-left: 5px;
|
||||
margin-bottom: 5px;
|
||||
height: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
109
src/components/blog/BlogCards/index.js
Normal file
109
src/components/blog/BlogCards/index.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import BlogCard from '../BlogCard';
|
||||
import BounceLoader from 'react-spinners/BounceLoader';
|
||||
|
||||
import {getBlogPosts, getTopic} from '../../../services/DiscourseService';
|
||||
export default function BlogCards({
|
||||
filterCallback
|
||||
}) {
|
||||
const [cardData, setCardData] = React.useState();
|
||||
const [loadingCards, setLoadingCards] = React.useState(true);
|
||||
|
||||
const getPosts = async () => {
|
||||
const data = await getBlogPosts(filterCallback.join(','));
|
||||
const resultset = []
|
||||
if (data.topics) {
|
||||
for (const topic of data.topics) {
|
||||
resultset.push(await getPostList(topic))
|
||||
}
|
||||
setCardData(resultset);
|
||||
} else {
|
||||
setCardData(undefined);
|
||||
}
|
||||
setLoadingCards(false);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
getPosts();
|
||||
setCardData(undefined);
|
||||
setLoadingCards(true);
|
||||
}, [filterCallback]);
|
||||
|
||||
if (cardData) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<div className={styles.gridContainer}>
|
||||
{cardData.map(function(a, index){
|
||||
return <BlogCard
|
||||
key={a.link}
|
||||
id={index + a.link}
|
||||
excerpt={a.excerpt}
|
||||
name={a.name}
|
||||
tags={a.tags}
|
||||
link={a.link}
|
||||
image={a.image}
|
||||
title={a.title}
|
||||
views={a.views}
|
||||
replies={a.replies}
|
||||
readTime={a.readTime}
|
||||
creatorImage={a.creatorImage}></BlogCard>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (loadingCards) {
|
||||
return (
|
||||
<BounceLoader
|
||||
className={styles.spinnerCenter}
|
||||
color={'#0033a1'}
|
||||
loading={true}
|
||||
size={150}
|
||||
aria-label="Loading Spinner"
|
||||
data-testid="loader"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.noFound}>
|
||||
{' '}
|
||||
No Blogposts Found with the Given Search Criteria
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getPostList(topic) {
|
||||
const fullTopic = await getTopic(topic.id);
|
||||
return {
|
||||
name: fullTopic.details.created_by.name,
|
||||
excerpt: styleExcerpt(topic.excerpt),
|
||||
creatorImage: getavatarURL(fullTopic.details.created_by.avatar_template),
|
||||
tags: topic.tags,
|
||||
image: fullTopic.image_url,
|
||||
link:
|
||||
'https://developer.sailpoint.com/discuss/t/' +
|
||||
topic.slug +
|
||||
'/' +
|
||||
topic.id,
|
||||
title: topic.title,
|
||||
views: fullTopic.views,
|
||||
liked: topic.like_count,
|
||||
replies: fullTopic.posts_count,
|
||||
solution: topic.has_accepted_answer,
|
||||
readTime: parseInt(fullTopic.word_count/100),
|
||||
};
|
||||
}
|
||||
|
||||
function getavatarURL(avatar) {
|
||||
return "https://developer.sailpoint.com" + avatar.replace("{size}", "120")
|
||||
}
|
||||
|
||||
function styleExcerpt(excerpt) {
|
||||
if (excerpt.length > 150) {
|
||||
return excerpt.slice(0, 150) + "..."
|
||||
} else {
|
||||
return excerpt.replace("…", "")
|
||||
}
|
||||
}
|
||||
|
||||
35
src/components/blog/BlogCards/styles.module.css
Normal file
35
src/components/blog/BlogCards/styles.module.css
Normal file
@@ -0,0 +1,35 @@
|
||||
/* Getting Started Card container */
|
||||
.gridContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
grid-template-columns: repeat(auto-fit, minmax(345px, 1fr));
|
||||
grid-gap: 40px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 0px auto;
|
||||
max-width: 1300px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.space {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.noFound {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
padding: 8px;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.spinnerCenter {
|
||||
margin: 100px auto;
|
||||
max-width: 1300px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
17
src/components/blog/BlogSidebar/BlogSidebarButton/index.js
Normal file
17
src/components/blog/BlogSidebar/BlogSidebarButton/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
|
||||
export default function BlogSidebarButton({
|
||||
filterCallback,
|
||||
text
|
||||
}) {
|
||||
const [isActive, setIsActive] = React.useState(false);
|
||||
const activeClass = isActive ? styles.tagSelected : ''
|
||||
function setFilters(e, f) {
|
||||
filterCallback(f)
|
||||
setIsActive(current => !current);
|
||||
}
|
||||
return <div key={text} onClick={(e) => setFilters(e, text)} className={activeClass + ' ' + styles.tag}>{text}</div>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
.tag {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
padding: 8px;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--dev-text-color-normal);
|
||||
transition: background-color 500ms;
|
||||
}
|
||||
|
||||
.tagSelected {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
background-color: var(--dev-secondary-text);
|
||||
color: var(--dev-card-background);
|
||||
padding: 8px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--dev-text-color-normal);
|
||||
transition: background-color 500ms;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--dev-text-color-normal);
|
||||
color: var(--dev-card-background);
|
||||
}
|
||||
|
||||
72
src/components/blog/BlogSidebar/index.js
Normal file
72
src/components/blog/BlogSidebar/index.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Link from '@docusaurus/Link';
|
||||
import { getTags } from '../../../services/DiscourseService';
|
||||
import BlogSidebarButton from './BlogSidebarButton';
|
||||
|
||||
export default function BlogSidebar({
|
||||
filterCallback
|
||||
}) {
|
||||
const [tagProductData, setTagProductData] = React.useState();
|
||||
const [tagTechnologyData, setTagTechnologyData] = React.useState();
|
||||
const [filterTags, setFilterTags] = React.useState(true);
|
||||
|
||||
const getTagData = async () => {
|
||||
const data = await getTags();
|
||||
const tagTechnologyResultset = []
|
||||
const tagProductResultset = []
|
||||
for (const tagGroup of data.extras.tag_groups) {
|
||||
if (tagGroup.id === 20) {
|
||||
for (const tag of tagGroup.tags) {
|
||||
tagProductResultset.push(tag.text)
|
||||
}
|
||||
}
|
||||
if (tagGroup.id === 17) {
|
||||
for (const tag of tagGroup.tags) {
|
||||
tagTechnologyResultset.push(tag.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
setTagProductData(tagProductResultset)
|
||||
setTagTechnologyData(tagTechnologyResultset)
|
||||
};
|
||||
|
||||
function toggleSeeAll() {
|
||||
filterTags ? setFilterTags(false) : setFilterTags(true)
|
||||
}
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
getTagData();
|
||||
}, []);
|
||||
|
||||
const filterText = filterTags ? 'See All Tags' : 'See Less Tags'
|
||||
|
||||
if (tagProductData && tagTechnologyData) {
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.tagHeader}>Blogs by Product</div>
|
||||
<div className={styles.tagContainer}>
|
||||
{tagProductData.map(function(a, index){
|
||||
return <BlogSidebarButton key={a} text={a} filterCallback={filterCallback}></BlogSidebarButton>
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.tagHeader}>Blogs by Identity Governance</div>
|
||||
<div className={styles.tagContainer}>
|
||||
{tagTechnologyData.map(function(a, index){
|
||||
return <div key={'div' + a} className={index > 10 && filterTags ? styles.hidden : ''} > <BlogSidebarButton key={a} text={a} filterCallback={filterCallback}></BlogSidebarButton></div>
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.seeAll} onClick={(e) => toggleSeeAll()}>
|
||||
{filterText}
|
||||
{/* <img className={styles.caretDown} src={useBaseUrl('/blog/caret-down-thin.svg')}></img> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
} else {
|
||||
return <div></div>;
|
||||
}
|
||||
}
|
||||
40
src/components/blog/BlogSidebar/styles.module.css
Normal file
40
src/components/blog/BlogSidebar/styles.module.css
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
.sidebar {
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.tagHeader {
|
||||
margin-top: 30px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tagContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.seeAll {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--dev-secondary-text);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
width: 100%;
|
||||
transition: background-color 500ms;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
.seeAll:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--dev-text-color-normal);
|
||||
color: var(--dev-card-background);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export default function DiscussCard({
|
||||
if (index > 2) {
|
||||
return '';
|
||||
}
|
||||
return <div className={styles.tag}>{tag}</div>;
|
||||
return <div key={tag} className={styles.tag}>{tag}</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,8 +33,9 @@
|
||||
cursor: pointer;
|
||||
top: -2px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
background-color: #c552ae10;
|
||||
background-color: var(--dev-button-hover);
|
||||
color: #cc27b0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
@@ -56,6 +57,7 @@
|
||||
box-shadow: 0px 20px 60px #00000015;
|
||||
border: 2px solid #df61ca;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.link {
|
||||
@@ -63,4 +65,5 @@
|
||||
}
|
||||
.link:hover {
|
||||
color: #df61ca;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
|
||||
/* UI Properties */
|
||||
background: var(--main-hero-card-background);
|
||||
box-shadow: var(--dev-card-shadow);
|
||||
box-shadow: var(--dev-main-card-shadow);
|
||||
border: 1px solid var(--dev-card-background);
|
||||
border-radius: 40px;
|
||||
opacity: 1;
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
.gridContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(195px, 1fr));
|
||||
grid-gap: 40px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 50px auto;
|
||||
max-width: 1000px;
|
||||
margin: 0px auto;
|
||||
max-width: 1300px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
22
src/components/marketplace/MarketplaceBanner/index.js
Normal file
22
src/components/marketplace/MarketplaceBanner/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
export default function MarketplaceBanner() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
<div className={styles.imageContainer}>
|
||||
<img className={styles.headerImage} src={useBaseUrl('/blog/marketplace_banner_template.png')}></img>
|
||||
<div className={styles.blogHeaderText}>
|
||||
Marketplace
|
||||
</div>
|
||||
</div >
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
.blogHeaderText {
|
||||
position: relative;
|
||||
color: #ffffff;
|
||||
font-size: 48px;
|
||||
max-width: 459px;
|
||||
font-weight: bold;
|
||||
line-height: 130%;
|
||||
top: -84px;
|
||||
left: 101px;
|
||||
}
|
||||
|
||||
.background {
|
||||
width: 100%;
|
||||
object-fit: repeat;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
background: rgb(0,51,161);
|
||||
background: linear-gradient(90deg, rgba(0,51,161,1) 0%, rgba(84,192,232,1) 100%);
|
||||
}
|
||||
|
||||
.headerImage {
|
||||
height: 90px;
|
||||
}
|
||||
68
src/components/marketplace/MarketplaceCard/index.js
Normal file
68
src/components/marketplace/MarketplaceCard/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
export default function MarketplaceCard({
|
||||
post,
|
||||
openDialogFunc,
|
||||
}) {
|
||||
|
||||
function setFilters(e) {
|
||||
openDialogFunc({"title": post.title, "image": post.image, "link": post.link, "id": post.id});
|
||||
}
|
||||
|
||||
let badge = (
|
||||
<div></div>
|
||||
);
|
||||
if (post.tags.includes("sailpoint-authored")) {
|
||||
badge = (
|
||||
<img
|
||||
className={styles.cardBadge}
|
||||
src={useBaseUrl('/icons/SailPoint-LogoIcon-RGB-Color.svg')}></img>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div onClick={(e) => setFilters(e)}>
|
||||
<div className={styles.card} >
|
||||
|
||||
<div className={styles.cardData}>
|
||||
<img className={styles.cardEye} src={useBaseUrl('/blog/eye-regular.svg')}></img>
|
||||
<div className={styles.cardCommentText}>{post.views}</div>
|
||||
<img className={styles.cardComment} src={useBaseUrl('/blog/comment-light.svg')}></img>
|
||||
<div className={styles.cardCommentText}>{post.replies}</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.cardUser}>
|
||||
<img className={styles.cardFace} src={useBaseUrl(post.creatorImage)}></img>
|
||||
<div className={styles.cardName}>{post.name}</div>
|
||||
</div>
|
||||
|
||||
|
||||
{badge}
|
||||
|
||||
<div className={styles.cardText}>
|
||||
<img className={styles.cardImage} src={useBaseUrl(post.image)}></img>
|
||||
<div className={styles.cardTitle}>{post.title}</div>
|
||||
<div className={styles.tags}>
|
||||
{post.tags?.map((tag, index) => {
|
||||
if (index > 2) {
|
||||
return '';
|
||||
}
|
||||
return <div key={tag} className={styles.tag}>{tag}</div>;
|
||||
})}
|
||||
</div>
|
||||
<ReactMarkdown className={styles.cardBody}>{post.excerpt}</ReactMarkdown>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
138
src/components/marketplace/MarketplaceCard/styles.module.css
Normal file
138
src/components/marketplace/MarketplaceCard/styles.module.css
Normal file
@@ -0,0 +1,138 @@
|
||||
/* Getting Started Card */
|
||||
.card {
|
||||
position: relative;
|
||||
margin-top: 20px;
|
||||
height: 650px;
|
||||
/* UI Properties */
|
||||
background: var(--dev-card-background);
|
||||
box-shadow: var(--dev-card-shadow);
|
||||
border: 1px solid var(--dev-card-background);
|
||||
border-radius: 40px;
|
||||
opacity: 1;
|
||||
transition: all 0.3s;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
cursor: pointer;
|
||||
transform: translate(0px, -5px);
|
||||
box-shadow: var(--dev-card-selected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.cardText {
|
||||
position: absolute;
|
||||
margin: 22px;
|
||||
min-width: 170px;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
|
||||
.cardTitle {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
background-color: var(--dev-tag-highlight);
|
||||
padding: 0px 8px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.cardUser {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 25px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cardFace {
|
||||
border-radius: 9999px;
|
||||
margin: auto;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.cardName {
|
||||
min-width: 170px;
|
||||
margin-top: 7px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.cardData {
|
||||
position: absolute;
|
||||
bottom: 50px;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cardEye {
|
||||
fill: #96a9bb;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
|
||||
margin-left: 5px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.cardComment {
|
||||
margin-left: 10px;
|
||||
fill: #96a9bb;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.cardCommentText {
|
||||
color: #96a9ba;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
margin-left: 5px;
|
||||
margin-bottom: 5px;
|
||||
height: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.cardBadge {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
bottom: 0px;
|
||||
margin-left: 5px;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
}
|
||||
108
src/components/marketplace/MarketplaceCardDetail/index.js
Normal file
108
src/components/marketplace/MarketplaceCardDetail/index.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import {addDarkToFileName} from '../../../util/util';
|
||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
||||
import 'react-tabs/style/react-tabs.css';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import BounceLoader from 'react-spinners/BounceLoader';
|
||||
export default function MarketplaceCardDetail({details, rawPost}) {
|
||||
const getDivText = (data, id) => {
|
||||
const requirementPosition = data.indexOf('id="' + id + '"');
|
||||
const requirementEndPosition = data.indexOf('</div>', requirementPosition);
|
||||
if (requirementPosition > 0 && requirementEndPosition > 0) {
|
||||
const validContent = data.substring(
|
||||
requirementPosition + 6 + id.length,
|
||||
requirementEndPosition,
|
||||
);
|
||||
const incorrectURLPattern = /upload:\/\/([^"]+)/g;
|
||||
const correctURLPattern =
|
||||
'https://developer.identitysoon.com/discuss/uploads/short-url/$1';
|
||||
return validContent.replace(incorrectURLPattern, correctURLPattern);
|
||||
} else {
|
||||
return 'No requirements found for this marketplace item';
|
||||
}
|
||||
// https://developer.identitysoon.com/discuss/uploads/short-url/f9rlt0eoj7RnpndvL0CHjcEesV0.png?dl=1\
|
||||
// upload://f9rlt0eoj7RnpndvL0CHjcEesV0.png
|
||||
};
|
||||
|
||||
const goToLink = (link) => {
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
|
||||
if (details) {
|
||||
return (
|
||||
<div className={styles.detailContainer}>
|
||||
<div className={styles.detailHeader}>
|
||||
<div className={styles.detailTitle}>{details.title}</div>
|
||||
<img
|
||||
className={styles.detailImage}
|
||||
src={useBaseUrl(details.image)}></img>
|
||||
</div>
|
||||
|
||||
<Tabs className={styles.detailTabs}>
|
||||
<TabList>
|
||||
<Tab>Details</Tab>
|
||||
<Tab>Requirements</Tab>
|
||||
<Tab>Support</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanel>
|
||||
<ReactMarkdown className={styles.detailTabContent}>
|
||||
{getDivText(rawPost, 'details')}
|
||||
</ReactMarkdown>
|
||||
<button
|
||||
className={styles.modalButton}
|
||||
onClick={async () => {
|
||||
goToLink(details.link);
|
||||
}}>
|
||||
<div className={styles.modalButtonText}>
|
||||
<img
|
||||
className={styles.buttonImage}
|
||||
src={useBaseUrl('/icons/discourse.svg')}></img>
|
||||
See More
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
className={styles.modalButton}
|
||||
onClick={async () => {
|
||||
console.log(getDivText(rawPost,'github'))
|
||||
const regex = /\(([^)]+)\)/;
|
||||
goToLink(getDivText(rawPost,'github').match(regex)[1]);
|
||||
}}>
|
||||
<div className={styles.modalButtonText}>
|
||||
<img
|
||||
className={styles.buttonImage}
|
||||
src={useBaseUrl('/icons/github.svg')}></img>
|
||||
GitHub
|
||||
</div>
|
||||
</button>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ReactMarkdown className={styles.detailTabContent}>
|
||||
{getDivText(rawPost, 'requirements')}
|
||||
</ReactMarkdown>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ReactMarkdown className={styles.detailTabContent}>
|
||||
{getDivText(rawPost, 'support')}
|
||||
</ReactMarkdown>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<BounceLoader
|
||||
className={styles.spinnerCenter}
|
||||
color={'#0033a1'}
|
||||
loading={true}
|
||||
size={150}
|
||||
aria-label="Loading Spinner"
|
||||
data-testid="loader"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
.detailContainer {
|
||||
max-width: 937px;
|
||||
}
|
||||
|
||||
.detailHeader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.detailImage {
|
||||
border-radius: 40px;
|
||||
max-width: 400px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.detailTitle {
|
||||
margin: 45px;
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
text-shadow: rgba(17, 61, 158, 0.64) 1px 2px 2px;
|
||||
}
|
||||
|
||||
.detailTabContent {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.detailTabs {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
|
||||
.modalButtonText {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.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: 125px;
|
||||
background-color: #ffffff31;
|
||||
}
|
||||
|
||||
.modalButton:hover {
|
||||
cursor: pointer;
|
||||
top: -2px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.4);
|
||||
background-color: #dae1e9;
|
||||
color: #005fc4;
|
||||
}
|
||||
|
||||
.spinnerCenter {
|
||||
margin: 30vh 20vh;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.detailImage {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonImage {
|
||||
position: relative;
|
||||
left: -7px;
|
||||
top: 4px;
|
||||
fill: #96a9bb;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
|
||||
margin-left: 5px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
154
src/components/marketplace/MarketplaceCards/index.js
Normal file
154
src/components/marketplace/MarketplaceCards/index.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import MarketplaceCard from '../MarketplaceCard';
|
||||
import Modal from 'react-modal';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import BounceLoader from 'react-spinners/BounceLoader';
|
||||
|
||||
import {
|
||||
getMarketplacePosts,
|
||||
getMarketplaceTopic,
|
||||
getMarketplaceTopicRaw,
|
||||
} from '../../../services/DiscourseService';
|
||||
import MarketplaceCardDetail from '../MarketplaceCardDetail';
|
||||
export default function MarketplaceCards({filterCallback}) {
|
||||
const [cardData, setCardData] = React.useState();
|
||||
const [detailsOpen, setDetailsOpen] = React.useState(false);
|
||||
const [details, setDetails] = React.useState('');
|
||||
const [loadingCards, setLoadingCards] = React.useState(true);
|
||||
|
||||
const getPosts = async () => {
|
||||
const data = await getMarketplacePosts(filterCallback.join(','));
|
||||
console.log(data)
|
||||
const resultset = [];
|
||||
if (data.topics) {
|
||||
for (const topic of data.topics) {
|
||||
if (topic.tags.length > 0) {
|
||||
resultset.push(await getPostList(topic));
|
||||
}
|
||||
}
|
||||
setCardData(resultset);
|
||||
} else {
|
||||
setCardData(undefined);
|
||||
}
|
||||
setLoadingCards(false);
|
||||
};
|
||||
|
||||
const getDetails = async (data) => {
|
||||
const raw = await getMarketplaceTopicRaw(data.id);
|
||||
setDetails({data: data, raw: raw});
|
||||
};
|
||||
|
||||
// callback that gets called when clicking on a card
|
||||
const openDialog = (data) => {
|
||||
setDetails({data: undefined, raw: undefined});
|
||||
getDetails(data);
|
||||
setDetailsOpen(true);
|
||||
};
|
||||
|
||||
React.useEffect(() => {}, [detailsOpen, details]);
|
||||
|
||||
Modal.setAppElement('#__docusaurus');
|
||||
React.useEffect(() => {
|
||||
getPosts();
|
||||
setCardData(undefined);
|
||||
setLoadingCards(true);
|
||||
}, [filterCallback]);
|
||||
|
||||
|
||||
const xImage = useBaseUrl('/icons/circle-xmark-regular.svg')
|
||||
|
||||
if (cardData) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<div className={styles.gridContainer}>
|
||||
{cardData.map(function (a, index) {
|
||||
return (
|
||||
<MarketplaceCard
|
||||
post={a}
|
||||
key={index + a.link}
|
||||
openDialogFunc={openDialog}></MarketplaceCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={detailsOpen}
|
||||
className={styles.modal}
|
||||
contentLabel="Details">
|
||||
<div>
|
||||
<div>
|
||||
<MarketplaceCardDetail
|
||||
details={details.data}
|
||||
rawPost={details.raw}></MarketplaceCardDetail>
|
||||
</div>
|
||||
<img
|
||||
className={styles.cardExit}
|
||||
src={xImage}
|
||||
onClick={async () => {
|
||||
setDetailsOpen(false);
|
||||
}}></img>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
} else if (loadingCards) {
|
||||
return (
|
||||
<BounceLoader
|
||||
className={styles.spinnerCenter}
|
||||
color={'#0033a1'}
|
||||
loading={true}
|
||||
size={150}
|
||||
aria-label="Loading Spinner"
|
||||
data-testid="loader"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.noFound}>
|
||||
{' '}
|
||||
No Marketplace Item Found with the Given Search Criteria
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getPostList(topic) {
|
||||
const fullTopic = await getMarketplaceTopic(topic.id);
|
||||
return {
|
||||
id: topic.id,
|
||||
name: fullTopic.details.created_by.name,
|
||||
excerpt: styleExcerpt(topic.excerpt),
|
||||
creatorImage: getavatarURL(fullTopic.details.created_by.avatar_template),
|
||||
tags: topic.tags,
|
||||
image: fullTopic.image_url,
|
||||
link:
|
||||
'https://developer.sailpoint.com/discuss/t/' +
|
||||
topic.slug +
|
||||
'/' +
|
||||
topic.id,
|
||||
title: topic.title,
|
||||
views: fullTopic.views,
|
||||
liked: topic.like_count,
|
||||
replies: fullTopic.posts_count,
|
||||
solution: topic.has_accepted_answer,
|
||||
readTime: parseInt(fullTopic.word_count / 100),
|
||||
};
|
||||
}
|
||||
|
||||
function getavatarURL(avatar) {
|
||||
return 'https://developer.sailpoint.com' + avatar.replace('{size}', '120');
|
||||
}
|
||||
|
||||
function styleExcerpt(excerpt) {
|
||||
if (excerpt) {
|
||||
// remove any strings that have colons between them
|
||||
excerpt = excerpt.replace(/:[^:]*:/g, '');
|
||||
if (excerpt.length > 150) {
|
||||
return excerpt.slice(0, 100) + '...';
|
||||
} else {
|
||||
return excerpt.replace('…', '');
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/* Getting Started Card container */
|
||||
.gridContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
grid-template-columns: repeat(auto-fit, minmax(345px, 1fr));
|
||||
grid-gap: 40px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 0px auto;
|
||||
max-width: 1300px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.space {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.noFound {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
padding: 8px;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.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%;
|
||||
max-width: 90%;
|
||||
min-height: 80%;
|
||||
scroll-behavior: auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.cardExit {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
fill: #96a9bb;
|
||||
/* computed from codepen https://codepen.io/sosuke/pen/Pjoqqp */
|
||||
filter: invert(79%) sepia(12%) saturate(484%) hue-rotate(168deg) brightness(84%) contrast(84%);
|
||||
margin-left: 5px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.cardExit:hover {
|
||||
top: 8px;
|
||||
cursor: pointer;
|
||||
height: 23px;
|
||||
width: 23px;
|
||||
}
|
||||
|
||||
|
||||
.spinnerCenter {
|
||||
margin: 100px auto;
|
||||
max-width: 1300px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
|
||||
export default function MarketplaceSidebarButton({
|
||||
filterCallback,
|
||||
text
|
||||
}) {
|
||||
const [isActive, setIsActive] = React.useState(false);
|
||||
const activeClass = isActive ? styles.tagSelected : ''
|
||||
function setFilters(e, f) {
|
||||
filterCallback(f)
|
||||
setIsActive(current => !current);
|
||||
}
|
||||
return <div key={text} onClick={(e) => setFilters(e, text)} className={activeClass + ' ' + styles.tag}>{text}</div>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
.tag {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-secondary-text);
|
||||
padding: 8px;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--dev-text-color-normal);
|
||||
transition: background-color 500ms;
|
||||
}
|
||||
|
||||
.tagSelected {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
background-color: var(--dev-secondary-text);
|
||||
color: var(--dev-card-background);
|
||||
padding: 8px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--dev-text-color-normal);
|
||||
transition: background-color 500ms;
|
||||
}
|
||||
|
||||
.tag:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--dev-text-color-normal);
|
||||
color: var(--dev-card-background);
|
||||
}
|
||||
|
||||
72
src/components/marketplace/MarketplaceSidebar/index.js
Normal file
72
src/components/marketplace/MarketplaceSidebar/index.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import Link from '@docusaurus/Link';
|
||||
import { getTags } from '../../../services/DiscourseService';
|
||||
import MarketplaceSidebarButton from './MarketplaceSidebarButton';
|
||||
|
||||
export default function MarketplaceSidebar({
|
||||
filterCallback
|
||||
}) {
|
||||
const [tagProductData, setTagProductData] = React.useState();
|
||||
const [tagTechnologyData, setTagTechnologyData] = React.useState();
|
||||
const [filterTags, setFilterTags] = React.useState(true);
|
||||
|
||||
const getTagData = async () => {
|
||||
const data = await getTags();
|
||||
const tagTechnologyResultset = []
|
||||
const tagProductResultset = []
|
||||
for (const tagGroup of data.extras.tag_groups) {
|
||||
if (tagGroup.id === 20) {
|
||||
for (const tag of tagGroup.tags) {
|
||||
tagProductResultset.push(tag.text)
|
||||
}
|
||||
}
|
||||
if (tagGroup.id === 17) {
|
||||
for (const tag of tagGroup.tags) {
|
||||
tagTechnologyResultset.push(tag.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
setTagProductData(tagProductResultset)
|
||||
setTagTechnologyData(tagTechnologyResultset)
|
||||
};
|
||||
|
||||
function toggleSeeAll() {
|
||||
filterTags ? setFilterTags(false) : setFilterTags(true)
|
||||
}
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
getTagData();
|
||||
}, []);
|
||||
|
||||
const filterText = filterTags ? 'See All Tags' : 'See Less Tags'
|
||||
|
||||
if (tagProductData && tagTechnologyData) {
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.tagHeader}>Items by Product</div>
|
||||
<div className={styles.tagContainer}>
|
||||
{tagProductData.map(function(a, index){
|
||||
return <MarketplaceSidebarButton key={a} text={a} filterCallback={filterCallback}></MarketplaceSidebarButton>
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.tagHeader}>Items by Identity Governance</div>
|
||||
<div className={styles.tagContainer}>
|
||||
{tagTechnologyData.map(function(a, index){
|
||||
return <div key={'div' + a} className={index > 10 && filterTags ? styles.hidden : ''} > <MarketplaceSidebarButton key={a} text={a} filterCallback={filterCallback}></MarketplaceSidebarButton></div>
|
||||
})}
|
||||
</div>
|
||||
<div className={styles.seeAll} onClick={(e) => toggleSeeAll()}>
|
||||
{filterText}
|
||||
{/* <img className={styles.caretDown} src={useBaseUrl('/blog/caret-down-thin.svg')}></img> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
} else {
|
||||
return <div></div>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
.sidebar {
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.tagHeader {
|
||||
margin-top: 30px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tagContainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.seeAll {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--dev-secondary-text);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
width: 100%;
|
||||
transition: background-color 500ms;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
.seeAll:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--dev-text-color-normal);
|
||||
color: var(--dev-card-background);
|
||||
}
|
||||
@@ -244,13 +244,16 @@
|
||||
--dev-text-color-secondary: #0033a1;
|
||||
|
||||
--dev-secondary-text: #415364;
|
||||
--dev-tag-highlight: #eaeef1;
|
||||
|
||||
--text-on-primary: #ffffff;
|
||||
|
||||
/*card css*/
|
||||
--dev-card-background: #ffffff;
|
||||
--dev-card-shadow: 0px 20px 60px #00000015;
|
||||
--dev-card-background: #e9e9e963;
|
||||
--dev-card-shadow: 0px 0px 0px #6b6b6b15;
|
||||
--dev-main-card-shadow: 0px 20px 60px #00000015;
|
||||
--dev-card-selected: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
--dev-button-hover: #c552ae10;
|
||||
|
||||
--ifm-github-logo: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E");
|
||||
--ifm-medium-logo: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 1770 1000"><circle cx="500" cy="500" r="500"/><ellipse ry="475" rx="250" cy="501" cx="1296"/><ellipse cx="1682" cy="502" rx="88" ry="424"/></svg>');
|
||||
@@ -283,9 +286,12 @@
|
||||
|
||||
/*card css*/
|
||||
--dev-card-background: #2a2b2d;
|
||||
--dev-card-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
--dev-card-selected: 0 5px 5px rgba(107, 107, 107, 0.2);
|
||||
--dev-card-shadow: 0 0px 0px rgba(0, 0, 0, 0.2);
|
||||
--dev-main-card-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
--dev-card-selected: 0 5px 5px rgba(151, 151, 151, 0.2);
|
||||
--dev-secondary-text: #dae1e9;
|
||||
--dev-tag-highlight: #00000075;
|
||||
--dev-button-hover: #c552ae40;
|
||||
|
||||
/*main hero card*/
|
||||
--main-hero-card-background: #202122;
|
||||
|
||||
64
src/pages/ambassador-program.js
Normal file
64
src/pages/ambassador-program.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import AmbassadorBanner from '../components/ambassador/AmbassadorBanner';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import styles from './ambassador.module.css';
|
||||
import AmbassadorCards from '../components/ambassador/AmbassadorCards';
|
||||
|
||||
export default function Ambassador() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
|
||||
return (
|
||||
<Layout description="The SailPoint Developer Community has everything you need to build, extend, and automate scalable identity solutions.">
|
||||
<main>
|
||||
{/* <AmbassadorBanner /> */}
|
||||
<div className={styles.ambassadorPurposeContainer}>
|
||||
<img
|
||||
className={styles.ambassadorPurposeImage}
|
||||
src={useBaseUrl('/ambassador/Ambassador-Program-Banner.png')}></img>
|
||||
<div className={styles.ambassadorPurposeText}>
|
||||
Our Ambassador Program offers not just exclusive benefits but also
|
||||
an enriching learning experience on SailPoint; it's a journey to
|
||||
expertise. As an Ambassador, your contributions foster community
|
||||
growth, enabling knowledge-sharing among experts like you.
|
||||
<Link
|
||||
className={styles.link}
|
||||
to="https://developer.sailpoint.com/discuss/t/announcing-the-developer-community-ambassador-program/10634">
|
||||
<div className={styles.button}>Become an Ambassador</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.headerText}>
|
||||
<div className={styles.headerTextOne}>Expert Ambassadors</div>
|
||||
</div>
|
||||
<div className={styles.imageContainer}>
|
||||
<div className={styles.spanLeft}></div>
|
||||
<img
|
||||
className={styles.ambassadorImage}
|
||||
src={useBaseUrl('/icons/ExpertAmbassador.svg')}></img>
|
||||
<div className={styles.spanLeft}></div>
|
||||
</div>
|
||||
<div className={styles.ambassadorCardContainer}>
|
||||
<AmbassadorCards expert={true} />
|
||||
</div>
|
||||
<div className={styles.headerText}>
|
||||
<div className={styles.headerTextOne}>Ambassadors</div>
|
||||
</div>
|
||||
<div className={styles.imageContainer}>
|
||||
<div className={styles.spanLeft}></div>
|
||||
<img
|
||||
className={styles.ambassadorImage}
|
||||
src={useBaseUrl('/icons/Ambassador.svg')}></img>
|
||||
<div className={styles.spanLeft}></div>
|
||||
</div>
|
||||
|
||||
<div className={styles.ambassadorCardContainer}>
|
||||
<AmbassadorCards expert={false} />
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
142
src/pages/ambassador.module.css
Normal file
142
src/pages/ambassador.module.css
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
.gridContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
grid-gap: 40px;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 50px auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.headerText {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
margin: 100px auto 0px auto;
|
||||
width: calc(100% - 100px);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.headerTextOne {
|
||||
color: var(--ifm-color-primary);
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.spanLeft {
|
||||
width: 35%;
|
||||
margin: 14px;
|
||||
position: relative;
|
||||
top: -45px;
|
||||
border-bottom-width:2px;
|
||||
border-bottom-style:solid;
|
||||
border-bottom-color:var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.imageContainer {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ambassadorImage {
|
||||
margin-left: 5px;
|
||||
padding: 20px;
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
|
||||
.ambassadorPurposeContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: calc(75% + 75px);
|
||||
align-items: center;
|
||||
margin: 0px auto 0px auto;
|
||||
}
|
||||
|
||||
|
||||
.ambassadorPurposeText {
|
||||
font-size: 21px;
|
||||
padding: 50px 0px 50px 50px;
|
||||
font-weight: 500;
|
||||
color: var(--dev-text-color-normal);
|
||||
}
|
||||
|
||||
.ambassadorPurposeImage {
|
||||
width: 40%;
|
||||
box-shadow: var(--dev-card-selected);
|
||||
border-radius: 40px;
|
||||
margin: 50px 50px 50px 0px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1460px) {
|
||||
.ambassadorPurposeContainer {
|
||||
flex-direction: column;
|
||||
}
|
||||
.ambassadorPurposeImage {
|
||||
width: 80%;
|
||||
margin: 50px 50px 50px 50px;
|
||||
}
|
||||
.ambassadorPurposeText {
|
||||
padding: 50px 50px 50px 50px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.button:hover {
|
||||
cursor: pointer;
|
||||
top: -2px;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2);
|
||||
background-color: #c552ae10;
|
||||
color: #cc27b0;
|
||||
background-color: var(--dev-button-hover);
|
||||
}
|
||||
|
||||
.button {
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
margin: 30px auto;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
|
||||
/* Layout Properties */
|
||||
width: 314px;
|
||||
height: 61px;
|
||||
line-height: 61px;
|
||||
/* UI Properties */
|
||||
background: transparent 0% 0% no-repeat padding-box;
|
||||
opacity: 1;
|
||||
|
||||
box-shadow: 0px 20px 60px #00000015;
|
||||
border: 2px solid #df61ca;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #df61ca;
|
||||
}
|
||||
.link:hover {
|
||||
color: #df61ca;
|
||||
text-decoration: none;
|
||||
}
|
||||
41
src/pages/blog.js
Normal file
41
src/pages/blog.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import BlogBanner from '../components/blog/BlogBanner';
|
||||
|
||||
import styles from './blog.module.css';
|
||||
import BlogCards from '../components/blog/BlogCards';
|
||||
import BlogSidebar from '../components/blog/BlogSidebar';
|
||||
|
||||
export default function Blog() {
|
||||
const [filteredProduct, setFilteredProduct] = React.useState([]);
|
||||
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
|
||||
const handleClick = (data) => {
|
||||
var tempFilter = filteredProduct.slice()
|
||||
|
||||
const index = tempFilter.indexOf(data);
|
||||
if (index !== -1) {
|
||||
tempFilter.splice(index, 1);
|
||||
} else {
|
||||
tempFilter.push(data)
|
||||
}
|
||||
|
||||
setFilteredProduct(tempFilter)
|
||||
};
|
||||
return (
|
||||
<Layout description="The SailPoint Developer Community has everything you need to build, extend, and automate scalable identity solutions.">
|
||||
<main>
|
||||
<BlogBanner />
|
||||
<div className={styles.blogContainer}>
|
||||
<div className={styles.blogSidbarContainer}><BlogSidebar filterCallback={handleClick}/></div>
|
||||
<div className={styles.blogCardContainer}><BlogCards filterCallback={filteredProduct}/></div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
17
src/pages/blog.module.css
Normal file
17
src/pages/blog.module.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.blogContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.blogSidbarContainer {
|
||||
flex: 5%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 870px) {
|
||||
.blogSidbarContainer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.blogCardContainer {
|
||||
flex: 95%;
|
||||
}
|
||||
41
src/pages/marketplace.js
Normal file
41
src/pages/marketplace.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import MarketplaceBanner from '../components/marketplace/MarketplaceBanner';
|
||||
|
||||
import styles from './marketplace.module.css';
|
||||
import MarketplaceCards from '../components/marketplace/MarketplaceCards';
|
||||
import MarketplaceSidebar from '../components/marketplace/MarketplaceSidebar';
|
||||
|
||||
export default function Marketplace() {
|
||||
const [filteredProduct, setFilteredProduct] = React.useState([]);
|
||||
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
|
||||
const handleClick = (data) => {
|
||||
var tempFilter = filteredProduct.slice()
|
||||
|
||||
const index = tempFilter.indexOf(data);
|
||||
if (index !== -1) {
|
||||
tempFilter.splice(index, 1);
|
||||
} else {
|
||||
tempFilter.push(data)
|
||||
}
|
||||
|
||||
setFilteredProduct(tempFilter)
|
||||
};
|
||||
return (
|
||||
<Layout description="The SailPoint Developer Community has everything you need to build, extend, and automate scalable identity solutions.">
|
||||
<main>
|
||||
<MarketplaceBanner />
|
||||
<div className={styles.blogContainer}>
|
||||
<div className={styles.blogSidbarContainer}><MarketplaceSidebar filterCallback={handleClick}/></div>
|
||||
<div className={styles.blogCardContainer}><MarketplaceCards filterCallback={filteredProduct}/></div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
17
src/pages/marketplace.module.css
Normal file
17
src/pages/marketplace.module.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.blogContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.blogSidbarContainer {
|
||||
flex: 5%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 870px) {
|
||||
.blogSidbarContainer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.blogCardContainer {
|
||||
flex: 95%;
|
||||
}
|
||||
@@ -8,3 +8,124 @@ export async function getTopPosts() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
export async function getAmbassadors(expert) {
|
||||
try {
|
||||
if (expert) {
|
||||
const response = await fetch(
|
||||
'https://developer.sailpoint.com/discuss/groups/ambassador_expert/members.json',
|
||||
);
|
||||
return await response.json();
|
||||
} else {
|
||||
const response = await fetch(
|
||||
'https://developer.sailpoint.com/discuss/groups/ambassador/members.json',
|
||||
);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAmbassadorDetails(id) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://developer.sailpoint.com/discuss/user-cards.json?user_ids=' + id.join(','),
|
||||
);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function checkImage(url) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
url,
|
||||
);
|
||||
console.log(response)
|
||||
return true
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function getBlogPosts(tags) {
|
||||
let url = ''
|
||||
if (tags) {
|
||||
url = 'https://developer.sailpoint.com/discuss/search.json?q=category:blog+tags:' + tags
|
||||
} else {
|
||||
url = 'https://developer.sailpoint.com/discuss/search.json?q=category:blog'
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
url,
|
||||
);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMarketplacePosts(tags) {
|
||||
let url = ''
|
||||
if (tags) {
|
||||
url = 'https://developer.sailpoint.com/discuss/search.json?q=category:marketplace+tags:' + tags
|
||||
} else {
|
||||
url = 'https://developer.sailpoint.com/discuss/search.json?q=category:marketplace'
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
url,
|
||||
);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTopic(id) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://developer.sailpoint.com/discuss/t/' + id + '.json',
|
||||
);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMarketplaceTopic(id) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://developer.sailpoint.com/discuss/t/' + id + '.json',
|
||||
);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMarketplaceTopicRaw(id) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://developer.sailpoint.com/discuss/raw/' + id + '.json',
|
||||
);
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTags() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
'https://developer.sailpoint.com/discuss/tags.json',
|
||||
);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
BIN
static/ambassador/Ambassador-Program-Banner.png
Normal file
BIN
static/ambassador/Ambassador-Program-Banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
static/ambassador/AmbassadorBadge-Pink-Hex.png
Normal file
BIN
static/ambassador/AmbassadorBadge-Pink-Hex.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
BIN
static/ambassador/AmbassadorBadge-QuadGrad-Squircle.png
Normal file
BIN
static/ambassador/AmbassadorBadge-QuadGrad-Squircle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
BIN
static/ambassador/ambassador_banner_template.png
Normal file
BIN
static/ambassador/ambassador_banner_template.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
@@ -50,6 +50,18 @@ get:
|
||||
|
||||
|
||||
**uncorrelated**: *eq*
|
||||
- in: query
|
||||
name: sorters
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: comma-separated
|
||||
example: id,name
|
||||
description: >-
|
||||
Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters#sorting-results)
|
||||
|
||||
|
||||
Sorting is supported for the following fields: **id**, **name**, **created**, **modified**
|
||||
responses:
|
||||
'200':
|
||||
description: List of account objects
|
||||
@@ -58,7 +70,7 @@ get:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
oneOf:
|
||||
anyOf:
|
||||
- $ref: '../schemas/SlimAccount.yaml'
|
||||
- $ref: '../schemas/FullAccount.yaml'
|
||||
examples:
|
||||
@@ -122,6 +134,8 @@ post:
|
||||
description: >-
|
||||
This API submits an account creation task and returns the task ID.
|
||||
|
||||
The `sourceId` where this account will be created must be included in the `attributes` object.
|
||||
|
||||
A token with ORG_ADMIN authority is required to call this API.
|
||||
security:
|
||||
- oauth2: [idn:accounts:manage]
|
||||
@@ -130,7 +144,7 @@ post:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/AccountAttributes.yaml'
|
||||
$ref: "../schemas/AccountAttributesCreate.yaml"
|
||||
responses:
|
||||
'202':
|
||||
description: Async task details
|
||||
|
||||
@@ -28,6 +28,9 @@ get:
|
||||
|
||||
|
||||
**category**: *eq*
|
||||
|
||||
|
||||
**features**: *ca*
|
||||
example: directConnect eq "true"
|
||||
- $ref: '../../v3/parameters/limit.yaml'
|
||||
- $ref: '../../v3/parameters/offset.yaml'
|
||||
|
||||
@@ -55,6 +55,13 @@ get:
|
||||
"name": "identity 1",
|
||||
"type": "IDENTITY"
|
||||
},
|
||||
"segments": [
|
||||
"1d126fe0-45e2-4aea-bc64-a07e9344ef26"
|
||||
],
|
||||
"manuallyUpdatedFields": {
|
||||
"DISPLAY_NAME": true,
|
||||
"DESCRIPTION": true
|
||||
},
|
||||
"id": "2c91808c74ff913f0175097daa9d59cd",
|
||||
"name": "LauncherTest1",
|
||||
"created": "2020-10-08T18:33:52.029Z",
|
||||
|
||||
@@ -55,6 +55,13 @@ get:
|
||||
"name": "identity 1",
|
||||
"type": "IDENTITY"
|
||||
},
|
||||
"segments": [
|
||||
"1d126fe0-45e2-4aea-bc64-a07e9344ef26"
|
||||
],
|
||||
"manuallyUpdatedFields": {
|
||||
"DISPLAY_NAME": true,
|
||||
"DESCRIPTION": true
|
||||
},
|
||||
"id": "2c91808c74ff913f0175097daa9d59cd",
|
||||
"name": "LauncherTest1",
|
||||
"created": "2020-10-08T18:33:52.029Z",
|
||||
|
||||
@@ -49,6 +49,13 @@ get:
|
||||
"name": "Addie Smith",
|
||||
"type": "IDENTITY"
|
||||
},
|
||||
"segments": [
|
||||
"1d126fe0-45e2-4aea-bc64-a07e9344ef26"
|
||||
],
|
||||
"manuallyUpdatedFields": {
|
||||
"DISPLAY_NAME": true,
|
||||
"DESCRIPTION": true
|
||||
},
|
||||
"id": "2c91808c74ff913f0175097daa9d59cd",
|
||||
"name": "LauncherTest1",
|
||||
"created": "2020-10-08T18:33:52.029Z",
|
||||
@@ -167,6 +174,22 @@ patch:
|
||||
}
|
||||
}
|
||||
]
|
||||
Set entitlement manually updated fields:
|
||||
description: >-
|
||||
This example shows how to set an entitlement's manually updated fields values with patch request.
|
||||
Values for all manually updateable fields must be specified in the request.
|
||||
For now only two entitlement fields support this: DISPLAY_NAME and DESCRIPTION.
|
||||
value:
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/manuallyUpdatedFields",
|
||||
"value": {
|
||||
"DISPLAY_NAME": true,
|
||||
"DESCRIPTION": true
|
||||
}
|
||||
}
|
||||
]
|
||||
responses:
|
||||
'200':
|
||||
description: Responds with the entitlement as updated.
|
||||
|
||||
@@ -25,7 +25,7 @@ get:
|
||||
name: configType
|
||||
required: true
|
||||
schema:
|
||||
$ref: '../schemas/work-reassignment/WorkTypeEnum.yaml'
|
||||
$ref: '../schemas/work-reassignment/ConfigTypeEnum.yaml'
|
||||
description: Reassignment work type
|
||||
example: accessRequests
|
||||
- in: query
|
||||
|
||||
@@ -2,7 +2,7 @@ get:
|
||||
tags:
|
||||
- Sources
|
||||
summary: Downloads source accounts schema template
|
||||
operationId: downloadSourceAccountsSchema
|
||||
operationId: getSourceAccountsSchema
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
@@ -37,7 +37,7 @@ post:
|
||||
summary: Uploads source accounts schema template
|
||||
description: >-
|
||||
This API uploads a source schema template file to configure a source's account attributes.
|
||||
operationId: uploadSourceAccountsSchema
|
||||
operationId: importSourceAccountsSchema
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#No Beta endpoint found for this operation.
|
||||
get:
|
||||
tags:
|
||||
- Sources
|
||||
summary: Downloads source entitlements schema template
|
||||
operationId: downloadSourceEntitlementsSchema
|
||||
operationId: getSourceEntitlementsSchema
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
@@ -37,13 +38,14 @@ get:
|
||||
$ref: '../../v3/responses/500.yaml'
|
||||
security:
|
||||
- oauth2: [idn:source-schema:read, idn:source-schema:manage]
|
||||
#No Beta endpoint found for this operation.
|
||||
post:
|
||||
tags:
|
||||
- Sources
|
||||
summary: Uploads source entitlements schema template
|
||||
description: >-
|
||||
This API uploads a source schema template file to configure a source's entitlement attributes.
|
||||
operationId: uploadSourceEntitlementsSchema
|
||||
operationId: importSourceEntitlementsSchema
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
post:
|
||||
operationId: uploadSourceConnectorFile
|
||||
operationId: importSourceConnectorFile
|
||||
security:
|
||||
- oauth2: [ idn:sources-admin:manage ]
|
||||
tags:
|
||||
- Sources
|
||||
summary: Upload connector file to source
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
get:
|
||||
operationId: getSource
|
||||
security:
|
||||
- oauth2: [ idn:sources:read ]
|
||||
tags:
|
||||
- Sources
|
||||
summary: Get Source by ID
|
||||
@@ -36,6 +38,8 @@ get:
|
||||
$ref: '../../v3/responses/500.yaml'
|
||||
put:
|
||||
operationId: putSource
|
||||
security:
|
||||
- oauth2: [ idn:sources:manage ]
|
||||
tags:
|
||||
- Sources
|
||||
summary: Update Source (Full)
|
||||
@@ -62,6 +66,7 @@ put:
|
||||
type: string
|
||||
required: true
|
||||
description: The Source id
|
||||
example: 2c9180835d191a86015d28455b4a2329
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -91,6 +96,8 @@ put:
|
||||
$ref: '../../v3/responses/500.yaml'
|
||||
patch:
|
||||
operationId: updateSource
|
||||
security:
|
||||
- oauth2: [ idn:sources:manage ]
|
||||
tags:
|
||||
- Sources
|
||||
summary: Update Source (Partial)
|
||||
@@ -99,7 +106,6 @@ patch:
|
||||
[JSON Patch](https://tools.ietf.org/html/rfc6902) standard.
|
||||
|
||||
Some fields are immutable and cannot be changed, such as:
|
||||
|
||||
* id
|
||||
* type
|
||||
* authoritative
|
||||
@@ -225,7 +231,9 @@ patch:
|
||||
'500':
|
||||
$ref: '../../v3/responses/500.yaml'
|
||||
delete:
|
||||
operationId: deleteSource
|
||||
security:
|
||||
- oauth2: [ idn:sources:manage ]
|
||||
operationId: delete
|
||||
tags:
|
||||
- Sources
|
||||
summary: Delete Source by ID
|
||||
@@ -270,13 +278,6 @@ delete:
|
||||
deleteSource:
|
||||
summary: Response returned when deleting a source
|
||||
value: {"type": "TASK_RESULT", "id": "2c91808779ecf55b0179f720942f181a", "name": null}
|
||||
links:
|
||||
GetTaskStatusById:
|
||||
operationId: getTaskStatus
|
||||
parameters:
|
||||
id: '$response.body#/id'
|
||||
description: >
|
||||
The `id` value returned in the response can be used as the `id` parameter in `GET /task-status/{id}`.
|
||||
'400':
|
||||
$ref: '../../v3/responses/400.yaml'
|
||||
'401':
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
get:
|
||||
operationId: listSources
|
||||
security:
|
||||
- oauth2: [ idn:sources:read ]
|
||||
tags:
|
||||
- Sources
|
||||
summary: Lists all sources in IdentityNow.
|
||||
@@ -18,51 +20,22 @@ get:
|
||||
example: name eq "#Employees"
|
||||
description: >-
|
||||
Filter results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters#filtering-results)
|
||||
|
||||
|
||||
|
||||
Filtering is supported for the following fields and operators:
|
||||
|
||||
|
||||
**id**: *eq, in*
|
||||
|
||||
|
||||
**name**: *co, eq, in, sw*
|
||||
|
||||
|
||||
**type**: *eq, in*
|
||||
|
||||
|
||||
**owner.id**: *eq, in*
|
||||
|
||||
|
||||
**features**: *ca, co*
|
||||
|
||||
|
||||
**created**: *eq*
|
||||
|
||||
|
||||
**modified**: *eq*
|
||||
|
||||
|
||||
**managementWorkgroup.id**: *eq*
|
||||
|
||||
|
||||
**description**: *eq*
|
||||
|
||||
|
||||
**authoritative**: *eq*
|
||||
|
||||
|
||||
**healthy**: *eq*
|
||||
|
||||
|
||||
**status**: *eq, in*
|
||||
|
||||
|
||||
**connectionType**: *eq*
|
||||
|
||||
|
||||
**connectorName**: *eq*
|
||||
**id**: *eq, in*
|
||||
**name**: *co, eq, in, sw*
|
||||
**type**: *eq, in*
|
||||
**owner.id**: *eq, in*
|
||||
**features**: *ca, co*
|
||||
**created**: *eq*
|
||||
**modified**: *eq*
|
||||
**managementWorkgroup.id**: *eq*
|
||||
**description**: *eq*
|
||||
**authoritative**: *eq*
|
||||
**healthy**: *eq*
|
||||
**status**: *eq, in*
|
||||
**connectionType**: *eq*
|
||||
**connectorName**: *eq*
|
||||
- in: query
|
||||
name: sorters
|
||||
schema:
|
||||
@@ -72,7 +45,6 @@ get:
|
||||
description: >-
|
||||
Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters#sorting-results)
|
||||
|
||||
|
||||
Sorting is supported for the following fields: **type, created, modified, name, owner.name, healthy, status**
|
||||
- in: query
|
||||
name: for-subadmin
|
||||
@@ -109,6 +81,8 @@ get:
|
||||
$ref: '../../v3/responses/500.yaml'
|
||||
post:
|
||||
operationId: createSource
|
||||
security:
|
||||
- oauth2: [ idn:sources:manage ]
|
||||
tags:
|
||||
- Sources
|
||||
summary: Creates a source in IdentityNow.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
type: object
|
||||
required:
|
||||
- attributes
|
||||
properties:
|
||||
attributes:
|
||||
description: The schema attribute values for the account
|
||||
type: object
|
||||
required:
|
||||
- sourceId
|
||||
properties:
|
||||
sourceId:
|
||||
type: string
|
||||
description: Target source to create an account
|
||||
example: 34bfcbe116c9407464af37acbaf7a4dc
|
||||
additionalProperties:
|
||||
type: string
|
||||
example:
|
||||
sourceId: 34bfcbe116c9407464af37acbaf7a4dc
|
||||
city: Austin
|
||||
displayName: John Doe
|
||||
userName: jdoe
|
||||
sAMAccountName: jDoe
|
||||
mail: john.doe@sailpoint.com
|
||||
@@ -206,3 +206,13 @@ allOf:
|
||||
type: string
|
||||
description: Name of the source
|
||||
example: Source with orphan entitlements
|
||||
mandatoryCommentRequirement:
|
||||
type: string
|
||||
description: >-
|
||||
Determines whether comments are required for decisions during certification reviews. You can require comments
|
||||
for all decisions, revoke-only decisions, or no decisions. By default, comments are not required for decisions.
|
||||
enum:
|
||||
- "ALL_DECISIONS"
|
||||
- "REVOKE_ONLY_DECISIONS"
|
||||
- "NO_DECISIONS"
|
||||
example: NO_DECISIONS
|
||||
@@ -13,11 +13,3 @@ properties:
|
||||
enum:
|
||||
- "CERTIFICATION"
|
||||
example: CERTIFICATION
|
||||
correlatedStatus:
|
||||
description: >-
|
||||
The correlatedStatus of the campaign. Only SOURCE_OWNER campaigns can be Uncorrelated.
|
||||
An Uncorrelated certification campaign only includes Uncorrelated identities (An identity is uncorrelated if it has no accounts on an authoritative source).
|
||||
enum:
|
||||
- "CORRELATED"
|
||||
- "UNCORRELATED"
|
||||
example: CORRELATED
|
||||
|
||||
@@ -8,6 +8,16 @@ properties:
|
||||
type: string
|
||||
description: The entitlement name
|
||||
example: "LauncherTest2"
|
||||
created:
|
||||
type: string
|
||||
description: Time when the entitlement was created
|
||||
format: 'date-time'
|
||||
example: "2020-10-08T18:33:52.029Z"
|
||||
modified:
|
||||
type: string
|
||||
description: Time when the entitlement was last modified
|
||||
format: 'date-time'
|
||||
example: "2020-10-08T18:33:52.029Z"
|
||||
attribute:
|
||||
type: string
|
||||
description: The entitlement attribute name
|
||||
@@ -20,28 +30,30 @@ properties:
|
||||
type: string
|
||||
description: The object type of the entitlement from the source schema
|
||||
example: "group"
|
||||
description:
|
||||
type: string
|
||||
description: The description of the entitlement
|
||||
example: "CN=LauncherTest2,OU=LauncherTestOrg,OU=slpt-automation,DC=TestAutomationAD,DC=local"
|
||||
privileged:
|
||||
type: boolean
|
||||
default: false
|
||||
description: True if the entitlement is privileged
|
||||
example: true
|
||||
cloudGoverned:
|
||||
type: boolean
|
||||
default: false
|
||||
description: True if the entitlement is cloud governed
|
||||
example: true
|
||||
created:
|
||||
description:
|
||||
type: string
|
||||
description: Time when the entitlement was created
|
||||
format: 'date-time'
|
||||
example: "2020-10-08T18:33:52.029Z"
|
||||
modified:
|
||||
type: string
|
||||
description: Time when the entitlement was last modified
|
||||
format: 'date-time'
|
||||
example: "2020-10-08T18:33:52.029Z"
|
||||
description: The description of the entitlement
|
||||
example: "CN=LauncherTest2,OU=LauncherTestOrg,OU=slpt-automation,DC=TestAutomationAD,DC=local"
|
||||
requestable:
|
||||
type: boolean
|
||||
default: false
|
||||
description: True if the entitlement is requestable
|
||||
example: true
|
||||
attributes:
|
||||
type: object
|
||||
description: A map of free-form key-value pairs from the source system
|
||||
example: { "fieldName": "fieldValue" }
|
||||
additionalProperties: true
|
||||
source:
|
||||
type: object
|
||||
properties:
|
||||
@@ -57,11 +69,12 @@ properties:
|
||||
type: string
|
||||
description: The source name
|
||||
example: ODS-AD-Source
|
||||
attributes:
|
||||
type: object
|
||||
description: A map of free-form key-value pairs from the source system
|
||||
example: { "fieldName": "fieldValue"}
|
||||
additionalProperties: true
|
||||
owner:
|
||||
$ref: '../schemas/gov-entitlement/OwnerReferenceDto.yaml'
|
||||
directPermissions:
|
||||
type: array
|
||||
items:
|
||||
$ref: './PermissionDto.yaml'
|
||||
segments:
|
||||
type: array
|
||||
items:
|
||||
@@ -72,9 +85,15 @@ properties:
|
||||
"f7b1b8a3-5fed-4fd4-ad29-82014e137e19",
|
||||
"29cb6c06-1da8-43ea-8be4-b3125f248f2a"
|
||||
]
|
||||
directPermissions:
|
||||
type: array
|
||||
items:
|
||||
$ref: './PermissionDto.yaml'
|
||||
owner:
|
||||
$ref: '../schemas/gov-entitlement/OwnerReferenceDto.yaml'
|
||||
manuallyUpdatedFields:
|
||||
description: >-
|
||||
Object contains entitlement manually updated fields.
|
||||
Field value is true if is was updated manually via entitlement import csv or patch endpoint.
|
||||
Field value is false if that property value has not been changed after first entitlement aggregation.
|
||||
Values for all manually updatable fields must be specified.
|
||||
For now only two entitlement fields support this: DISPLAY_NAME and DESCRIPTION.
|
||||
example: {
|
||||
"DISPLAY_NAME": true,
|
||||
"DESCRIPTION": true
|
||||
}
|
||||
$ref: '../schemas/gov-entitlement/ManuallyUpdatedFieldsDTO.yaml'
|
||||
|
||||
@@ -6,4 +6,5 @@ enum:
|
||||
- CCG
|
||||
- VA
|
||||
- INTERNAL
|
||||
- IIQ_HARVESTER
|
||||
- null
|
||||
|
||||
@@ -4,6 +4,7 @@ properties:
|
||||
type: string
|
||||
description: Policy id
|
||||
example: "0f11f2a4-7c94-4bf3-a2bd-742580fe3bde"
|
||||
readOnly: true
|
||||
name:
|
||||
type: string
|
||||
description: Policy Business Name
|
||||
@@ -13,21 +14,25 @@ properties:
|
||||
format: date-time
|
||||
description: The time when this SOD policy is created.
|
||||
example: "2020-01-01T00:00:00.000000Z"
|
||||
readOnly: true
|
||||
modified:
|
||||
type: string
|
||||
format: date-time
|
||||
description: The time when this SOD policy is modified.
|
||||
example: "2020-01-01T00:00:00.000000Z"
|
||||
readOnly: true
|
||||
description:
|
||||
type: string
|
||||
description: Optional description of the SOD policy
|
||||
example: "This policy ensures compliance of xyz"
|
||||
nullable: true
|
||||
ownerRef:
|
||||
$ref: '../../v3/schemas/BaseReferenceDto.yaml'
|
||||
externalPolicyReference:
|
||||
type: string
|
||||
description: Optional External Policy Reference
|
||||
example: "XYZ policy"
|
||||
nullable: true
|
||||
policyQuery:
|
||||
type: string
|
||||
description: Search query of the SOD policy
|
||||
@@ -36,10 +41,12 @@ properties:
|
||||
type: string
|
||||
description: Optional compensating controls(Mitigating Controls)
|
||||
example: "Have a manager review the transaction decisions for their \"out of compliance\" employee"
|
||||
nullable: true
|
||||
correctionAdvice:
|
||||
type: string
|
||||
description: Optional correction advice
|
||||
example: "Based on the role of the employee, managers should remove access that is not required for their job function."
|
||||
nullable: true
|
||||
state:
|
||||
type: string
|
||||
description: whether the policy is enforced or not
|
||||
@@ -57,11 +64,13 @@ properties:
|
||||
type: string
|
||||
description: Policy's creator ID
|
||||
example: "0f11f2a4-7c94-4bf3-a2bd-742580fe3bde"
|
||||
readOnly: true
|
||||
modifierId:
|
||||
type: string
|
||||
description: Policy's modifier ID
|
||||
example: "0f11f2a4-7c94-4bf3-a2bd-742580fe3bde"
|
||||
nullable : true
|
||||
readOnly: true
|
||||
violationOwnerAssignmentConfig:
|
||||
$ref: './ViolationOwnerAssignmentConfig.yaml'
|
||||
nullable: true
|
||||
@@ -69,6 +78,7 @@ properties:
|
||||
type: boolean
|
||||
description: defines whether a policy has been scheduled or not
|
||||
example: true
|
||||
default: false
|
||||
type:
|
||||
type: string
|
||||
description: whether a policy is query based or conflicting access based
|
||||
@@ -78,5 +88,6 @@ properties:
|
||||
- CONFLICTING_ACCESS_BASED
|
||||
example: GENERAL
|
||||
conflictingAccessCriteria:
|
||||
$ref: './ConflictingAccessCriteria.yaml'
|
||||
nullable: true
|
||||
allOf:
|
||||
- $ref: './ConflictingAccessCriteria.yaml'
|
||||
- nullable: true
|
||||
|
||||
@@ -12,10 +12,18 @@ properties:
|
||||
type: string
|
||||
description: The connector script name
|
||||
example: "servicenow"
|
||||
features:
|
||||
type: array
|
||||
description: The list of features supported by the connector
|
||||
nullable: true
|
||||
items:
|
||||
type: string
|
||||
example: ["PROVISIONING", "SYNC_PROVISIONING", "SEARCH", "UNSTRUCTURED_TARGETS"]
|
||||
directConnect:
|
||||
type: boolean
|
||||
description: true if the source is a direct connect source
|
||||
example: true
|
||||
default: false
|
||||
connectorMetadata:
|
||||
type: object
|
||||
description: Object containing metadata pertinent to the UI to be used
|
||||
|
||||
@@ -5,6 +5,7 @@ properties:
|
||||
enum:
|
||||
- MANAGER
|
||||
- STATIC
|
||||
- null
|
||||
description: >-
|
||||
Details about the violations owner.
|
||||
|
||||
@@ -12,6 +13,9 @@ properties:
|
||||
|
||||
STATIC - Governance Group or Identity
|
||||
example: MANAGER
|
||||
nullable: true
|
||||
ownerRef:
|
||||
$ref: '../../v3/schemas/BaseReferenceDto.yaml'
|
||||
allOf:
|
||||
- $ref: '../../v3/schemas/BaseReferenceDto.yaml'
|
||||
- nullable: true
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ value:
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
|
||||
|
||||
@@ -43,4 +43,5 @@ value:
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
@@ -42,4 +42,5 @@ value:
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
correlatedStatus: CORRELATED
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
@@ -38,5 +38,6 @@ value:
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
- id: b7e6459eed5247ac8b98a5fed81fe27f
|
||||
name: Reporting Access Review
|
||||
created: 2022-07-28T19:19:40.035Z
|
||||
@@ -77,6 +78,7 @@
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
- id: b9f41bc69e7a4291b9de0630396d030d
|
||||
name: Campaign With Admin Role
|
||||
created: 2022-08-02T13:40:36.857Z
|
||||
@@ -120,6 +122,7 @@
|
||||
emailNotificationEnabled: false
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
- id: b9f41bc69e7a4291b9de0630396d030d
|
||||
name: AD Source Review
|
||||
created: 2022-08-02T13:40:36.857Z
|
||||
@@ -158,4 +161,5 @@
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
correlatedStatus: CORRELATED
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
@@ -9,6 +9,7 @@ value:
|
||||
filter:
|
||||
type: CAMPAIGN_FILTER
|
||||
id: 0c46fb26c6b20967a55517ee90d15b93
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,4 +16,5 @@ value:
|
||||
name: SailPoint Support
|
||||
roleIds:
|
||||
- b15d609fc5c8434b865fe552315fda8f
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
|
||||
@@ -12,4 +12,5 @@ value:
|
||||
searchCampaignInfo:
|
||||
type: ACCESS
|
||||
query: user
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
|
||||
@@ -13,4 +13,5 @@ value:
|
||||
sourceIds:
|
||||
- 612b31b1a0f04aaf83123bdb80e70db6
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
|
||||
@@ -15,4 +15,5 @@ value:
|
||||
id: e0adaae69852e8fe8b8a3d48e5ce757c
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
recommendationsEnabled: false
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
@@ -16,4 +16,5 @@ value:
|
||||
id: 2c9180876ab2c053016ab6f65dfd5aaa
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
recommendationsEnabled: false
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
@@ -18,4 +18,5 @@ value:
|
||||
description: Identities with reporting abilities
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
recommendationsEnabled: false
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
@@ -18,4 +18,5 @@ value:
|
||||
emailNotificationEnabled: true
|
||||
autoRevokeAllowed: false
|
||||
recommendationsEnabled: false
|
||||
correlatedStatus: CORRELATED
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
@@ -22,3 +22,4 @@ value:
|
||||
totalCertifications: 0
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: null
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
@@ -32,4 +32,5 @@ value:
|
||||
totalCertifications: 0
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: null
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
@@ -31,3 +31,4 @@ value:
|
||||
totalCertifications: 0
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: null
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
@@ -25,4 +25,5 @@ value:
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: null
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
|
||||
@@ -38,6 +38,7 @@ value:
|
||||
totalCertifications: 0
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: null
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
- id: 1be8fc1103914bf0a4e14e316b6a7b7c
|
||||
name: Manager Review
|
||||
description: A review of everyone's access by their manager.
|
||||
@@ -61,6 +62,7 @@ value:
|
||||
totalCertifications: 5
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: []
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
- id: 7e1a731e3fb845cfbe58112ba4673ee4
|
||||
name: Search Campaign
|
||||
description: Search Campaign for Identities
|
||||
@@ -93,6 +95,7 @@ value:
|
||||
totalCertifications: 6
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: []
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
- id: ad3cf3dd50394b1bad646de4bc51b999
|
||||
name: Source Owner Campaign
|
||||
description: Example for Source Owner Campaign
|
||||
@@ -118,4 +121,5 @@ value:
|
||||
totalCertifications: 2
|
||||
completedCertifications: 0
|
||||
sourcesWithOrphanEntitlements: []
|
||||
correlatedStatus: CORRELATED
|
||||
correlatedStatus: CORRELATED
|
||||
mandatoryCommentRequirement: NO_DECISIONS
|
||||
@@ -0,0 +1,20 @@
|
||||
type: object
|
||||
properties:
|
||||
DISPLAY_NAME:
|
||||
type: boolean
|
||||
default: false
|
||||
description: >-
|
||||
True if the entitlements name was updated manually via entitlement import csv or patch endpoint.
|
||||
False means that property value has not been change after first entitlement aggregation.
|
||||
|
||||
Field refers to [Entitlement response schema](https://developer.sailpoint.com/idn/api/beta/get-entitlement) > `name` property.
|
||||
example: true
|
||||
DESCRIPTION:
|
||||
type: boolean
|
||||
default: false
|
||||
description: >-
|
||||
True if the entitlement description was updated manually via entitlement import csv or patch endpoint.
|
||||
False means that property value has not been change after first entitlement aggregation.
|
||||
|
||||
Field refers to [Entitlement response schema](https://developer.sailpoint.com/idn/api/beta/get-entitlement) > `description` property.
|
||||
example: true
|
||||
@@ -1,7 +1,7 @@
|
||||
type: string
|
||||
description: Enum list of valid work types that can be selected for a Reassignment Configuration
|
||||
enum:
|
||||
- accessRequests
|
||||
- certifications
|
||||
- manualTasks
|
||||
example: accessRequests
|
||||
- ACCESS_REQUESTS
|
||||
- CERTIFICATIONS
|
||||
- MANUAL_TASKS
|
||||
example: ACCESS_REQUESTS
|
||||
@@ -8,4 +8,4 @@ properties:
|
||||
name:
|
||||
type: string
|
||||
description: Human-readable display name of the object
|
||||
example: William.Wilson
|
||||
example: William Wilson
|
||||
@@ -1,7 +0,0 @@
|
||||
type: string
|
||||
description: Enum list of valid work types that can be selected for a Reassignment Configuration
|
||||
enum:
|
||||
- accessRequests
|
||||
- certifications
|
||||
- manualTasks
|
||||
example: accessRequests
|
||||
@@ -1568,7 +1568,7 @@ paths:
|
||||
$ref: './beta/paths/sod-violation-report.yaml'
|
||||
/sod-violations/predict:
|
||||
$ref: './beta/paths/sod/predict-violations.yaml'
|
||||
/sod-violation-report-status/{reportResultId}:
|
||||
/sod-policies/sod-violation-report-status/{reportResultId}:
|
||||
$ref: './beta/paths/sod-violation-report-status.yaml'
|
||||
/sod-violation-report/run:
|
||||
$ref: './beta/paths/sod-all-report-run.yaml'
|
||||
|
||||
@@ -927,12 +927,6 @@ paths:
|
||||
$ref: './v3/paths/password-org-config.yaml'
|
||||
/sod-config/public-keys/target:
|
||||
$ref: "./v3/paths/sod-public-key-target.yaml"
|
||||
/sod-exceptions:
|
||||
$ref: "./v3/paths/sod-exceptions.yaml"
|
||||
/sod-exceptions/{id}:
|
||||
$ref: "./v3/paths/sod-exception.yaml"
|
||||
/sod-exceptions/bulk-create:
|
||||
$ref: "./v3/paths/sod-exceptions-bulk-create.yaml"
|
||||
/sod-policies:
|
||||
$ref: './v3/paths/sod-policies.yaml'
|
||||
/sod-policies/{id}:
|
||||
@@ -947,7 +941,7 @@ paths:
|
||||
$ref: './v3/paths/sod-violation-report.yaml'
|
||||
/sod-risks/risks/{id}:
|
||||
$ref: "./v3/paths/sod-arm-risk.yaml"
|
||||
/sod-violation-report-status/{reportResultId}:
|
||||
/sod-policies/sod-violation-report-status/{reportResultId}:
|
||||
$ref: './v3/paths/sod-violation-report-status.yaml'
|
||||
/sod-violations/predict:
|
||||
$ref: "./v3/paths/sod-violations-predict.yaml"
|
||||
|
||||
@@ -44,6 +44,18 @@ get:
|
||||
|
||||
**uncorrelated**: *eq*
|
||||
required: false
|
||||
- in: query
|
||||
name: sorters
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: comma-separated
|
||||
example: id,name
|
||||
description: >-
|
||||
Sort results using the standard syntax described in [V3 API Standard Collection Parameters](https://developer.sailpoint.com/idn/api/standard-collection-parameters#sorting-results)
|
||||
|
||||
|
||||
Sorting is supported for the following fields: **id**, **name**, **created**, **modified**
|
||||
responses:
|
||||
"200":
|
||||
description: List of account objects
|
||||
@@ -71,6 +83,8 @@ post:
|
||||
description: >-
|
||||
This API submits an account creation task and returns the task ID.
|
||||
|
||||
The `sourceId` where this account will be created must be included in the `attributes` object.
|
||||
|
||||
A token with ORG_ADMIN authority is required to call this API.
|
||||
security:
|
||||
- oauth2: [idn:accounts:manage]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user