@stack-spot/portal-extension
v0.3.0
Published
A Stackspot extension is an independent application that runs inside the Stackspot Portal (EDP). This application runs in a separate environment and doesn't have access to the portal itself, but it can interact with many Stackspot APIs, show toasters and
Downloads
240
Maintainers
Keywords
Readme
Stackspot extension
A Stackspot extension is an independent application that runs inside the Stackspot Portal (EDP). This application runs in a separate environment and doesn't have access to the portal itself, but it can interact with many Stackspot APIs, show toasters and open modals through this SDK.
This SDK is responsible for communicating with the Stackspot Portal through messages. This message exchange is invisible to the developer, who can just call the method in the SDK and expect all the action to just happen!
What this the SDK do?
- It allows the control of modals and toasters, including alerts, confirmation dialogs and floating side panels.
- It allows access to most of the Stackspot API (backend) through simple methods.
- Provides peer dependencies that sets up the frontend project to look just as part of the Stackspot portal as any native page.
What doesn't it do?
- It does not provide a boilerplate for starting up the project.
- It does not change any menu item of the parent portal.
- It doesn't tell where the extension goes in the portal.
- It doesn't provide a navigation system.
What can I do with extensions:
- Create a new page that can be accessed through a new menu and/or URL. The menu is setup in the Account portal.
- Create widgets to show in dashboards of workspaces.
- Interact with any Stackspot API, as longs as the user logged in has access to it.
Required libraries for the extension:
- React.
- Citric: design system.
- Portal theme: theme for the design system.
- Portal translate: internationalization mechanism.
- React Query: global network state manager.
- Styled components: helps styling our components.
Although all these dependencies are required to exist in the extension project, you are not required to use Portal translate, React Query or Styled components if you don't want to.
Recommended libraries:
- Citron navigator: if your extension has multiple pages, it is a good idea to handle navigation with the Citron Navigator, an in-house developed navigator for React.
- Portal components: a set of components used by every Stackspot portal built o top of Citric. Use it only if you need some of the components of this library.
Installation
pnpm add @stack-spot/portal-extension
We recommend using pnpm, but you can also use npm or yarn.
Exposing the project as an extension:
In your entrypoint file (e.g. App.tsx):
import { StackspotExtension } from '@stack-spot/portal-extension'
export const App = () => (
<StackspotExtension>
{/* Your content goes here */}
</StackspotExtension>
)
Showing modal content
import { StackspotExtension } from '@stack-spot/portal-extension'
import { Button } from '@citric/core'
import { alert, confirm, showModal, showRightPanel } from '@stack-spot/portal-extension'
export const App = () => (
<StackspotExtension>
<Button
onClick={() => alert({
title: 'Alert',
subtitle: 'This is an alert!',
})}
>
Show alert
</Button>
<Button
onClick={async () => {
const answer = await confirm({
title: 'Confirm',
subtitle: 'Do you confirm?',
})
console.log(answer ? 'confirmed' : 'canceled')
}}
>
Show confirm
</Button>
<Button
onClick={() => showModal({
title: 'My modal',
subtitle: 'This is an example of modal',
path: '/path-to-modal',
})}
>
Show modal
</Button>
<Button
onClick={() => showRightPanel({
title: 'Meu painel',
subtitle: 'This is an example of right panel',
path: '/path-to-right-panel',
})}
>
Show floating side panel on the right
</Button>
</StackspotExtension>
)
Notice that modals and right panels load other views of this same application. In order to have multiple views, we suggest using a React Navigator (Citron Navigator is our recommendation).
Showing toasters
import { StackspotExtension } from '@stack-spot/portal-extension'
import { Button } from '@citric/core'
import { showToaster } from '@stack-spot/portal-extension'
export const App = () => (
<StackspotExtension>
<Button onClick={() => showToaster({ message: 'Hello World' })}>
Show an info toast
</Button>
<Button onClick={() => showToaster({ message: 'Oops! An error.', type: 'error' })}>
Show an error toast
</Button>
</StackspotExtension>
)
Performing network requests to the Stackspot API
As an extension of the portal, you don't have access to the user's access token and cannot make direct requests to the Stackspot API. Instead, you must import a network client of the SDK and use it instead. Let's see an example of how to list all studio's name for the user currently logged in:
import { StackspotExtension } from '@stack-spot/portal-extension'
import { Button, Text } from '@citric/core'
import { showToaster, contentClient } from '@stack-spot/portal-extension'
export const App = () => {
const [studios, isLoading, error] = contentClient.studios.useStatefulQuery({})
return (
<StackspotExtension>
<h1>Widget</h1>
{isLoading && <Text>Loading...</Text>}
{error && <Text>{`${error}`}</Text>}
{studios && <ul>{studios.map(s => <li key={s.id}>{s.name}</li>)}</ul>}
</div>
</StackspotExtension>
)
}
Above, we're using React Query to manage our request state. This is recommended, but if you want to use something else, you can always make the query yourself:
import { StackspotExtension } from '@stack-spot/portal-extension'
import { Button, Text } from '@citric/core'
import { showToaster, contentClient } from '@stack-spot/portal-extension'
export const App = () => {
const [data, setData] = useState<any[]>() // any is not ideal here, this is just an example
const [isLoading, setLoading] = useState(true)
const [error, setError] = useState('')
useEffect(() => {
(async () => {
setLoading(true)
try {
const result = await contentClient.studios.query({})
setData(result)
} catch (e: any) {
setError(e.message || `${e}`)
}
setLoading(false)
})()
}, [])
const [studios, isLoading, error] = contentClient.studios.useStatefulQuery({})
return (
<StackspotExtension>
<h1>Widget</h1>
{isLoading && <Text>Loading...</Text>}
{error && <Text>{`${error}`}</Text>}
{studios && <ul>{studios.map(s => <li key={s.id}>{s.name}</li>)}</ul>}
</div>
</StackspotExtension>
)
}
It is much harder to control the request state yourself, but it can be done. In general, prefer to use React Query hooks and use query
,
directly, only if outside a React component.
Network clients
The SDK exposes the following network clients:
- accountClient
- agentClient
- aiClient
- cloudAccountClient
- cloudPlatformClient
- cloudPlatformHorizonClient
- cloudRuntimesClient
- cloudServicesClient
- contentClient
- eventBusClient
- insightsClient
- notificationClient
- runtimeManagerClient
- secretsClient
- workflowClient
- workspaceClient
- workspaceManagerClient
- workspaceSearchClient
Each client exposes access to a Stackspot API. The documentation of each one is still in progress, but you can use the Typescript auto complete to check the code documentation and methods of each client. If necessary, you can install "@stack-spot/portal-network" as a dev dependency to use its types.
The methods in a client can be either queries or mutations. Queries fetch data from the backend, without any side-effect, while mutations can change data in the backend. A query will have the functions: query, useQuery (uses React Suspense), useStatefulQuery (doesn't use React Suspense), isAllowed (checks if the method is allowed for the user), useAllowed and invalidate (invalidates any cached data for that query). Mutations are similar, but they can't be invalidated and, instead of query, useQuery and useStatefulQuery, we have mutate and useMutation.
For more documentation on the hooks useQuery and useMutation, we refer to the React Query's documentation.
Sample project
In order to quick start an extension project, we have a repository with boilerplate code that can be copied:
https://github.com/Tiagoperes/stackspot-extension-sample