npm package: @stack-spot/portal-extension
Description: 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 
Latest version: 1.0.2

# 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 does 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](https://pnpm.io/pt/), but you can also use npm or yarn.

## Exposing the project as an extension:
In your entrypoint file (e.g. App.tsx):
```tsx
import { StackspotExtension } from '@stack-spot/portal-extension'

export const App = () => (
  <StackspotExtension>
    {/* Your content goes here */}
  </StackspotExtension>
)
```

## Showing modal content

```tsx
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

```tsx
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>
)
```

### Creating menus
From the extension, the developer can create a context menu for its page. This menu appears as a side bar on the right, just next to main
sections menu.

It's important to notice some limitations:
1. The sections menu (main menu) can't be changed by an extension.
2. The menu is represented by a predefined object template, i.e. the user is not free to create whenever he/she wants. It must follow a
well-defined structure.
3. Only full-page extensions can alter the menu, e.g., if the extension is loaded as a widget by the parent, the context menu won't show.

To create a menu is quite simple: just call `showMenu(options)`. See an example below:

```ts
import { showMenu } from '@stack-spot/portal-extension'
import { root } from 'navigation'

showMenu({
  options: [
    {
      label: 'Digital Store',
      children: [
        { label: 'Home', href: root.$link(), active: true },
        { label: 'Issues', href: root.issues.$link() },
        { label: 'Management', href: root.management.$link() },
      ]
    },
    {
      label: 'Links Externos',
      open: false,
      children: [
        { label: 'Stackspot', href: 'https://www.stackspot.com', target: '_blank' },
        { label: 'Zup', href: 'https://www.zup.com.br', target: '_blank' },
        { label: 'Google', href: 'https://www.google.com', target: '_blank' },
        { label: 'Microsoft', href: 'https://www.microsoft.com' },
        { label: 'Apple', href: 'https://www.apple.com' },
      ]
    },
  ]
})
```

The context menu supports a varied set of features, we recommend reading the type `MenuSectionContent` in
[its type definitions](https://github.com/stack-spot/portal-extensions/tree/main/packages/commons/src/menu-types.ts) for more details.

Once shown, the menu only changes if `showMenu` is called again with another value. The menu disappears if the user leaves the extension
page or if the developer explicitly calls `closeMenu()`.

## 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:

```tsx
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:

```tsx
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.

## Retrieving data from the logged user

For security reasons, we do not expose tokens or any sensitive session details. 
However, you can retrieve the email and name of the currently logged-in user from the `getUserData()` function as follows:

```tsx
import { getUserData } from '@stack-spot/portal-extension'
const { email, name } = await getUserData()
```

### Network clients
The SDK exposes the following network clients:

- accountClient
- agentClient
- aiClient
- cloudAccountClient
- cloudPlatformClient
- cloudRuntimesClient
- 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](https://tanstack.com/query/v5/docs/framework/react/overview).

## Previewing an extension
You can preview your extension inside the EDP Portal by creating a `preview.json` file inside the root of your project. See the example
below:

```json
{
  "slug": "digital-store",
  "displayName": {
    "pt": "Loja digital",
    "en": "Digital Store"
  },
  "url": "http://localhost:3001",
  "links": [
    {
      "type": "section"
    }
  ]
}
```

- slug: the identifier of the extension;
- displayName: the name to appear in the menu or dashboard;
- url: the url where the extension is running;
- links: where the extension should be included. In this example, we want to include just a link in the main menu (sections).

Now that you have a `preview.json` file, you can preview your project by:
1. Run the project locally at the address specified in "url".
1. Run `pnpm preview-extension`.

To stop previewing an extension, run `pnpm remove-extension-preview`.

## Sample project
In order to quick start an extension project, we have a repository with boilerplate code that can be copied:

https://github.com/stack-spot/edp-extension-sample