Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/agentstack-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@carbon/react": "catalog:",
"@carbon/styles": "^1.96.0",
"@floating-ui/react": "^0.27.16",
"@hookform/resolvers": "^5.2.2",
"@ibm/plex": "^6.4.1",
"@radix-ui/react-slot": "^1.2.4",
"@tanstack/react-query": "catalog:",
Expand Down
6 changes: 4 additions & 2 deletions apps/agentstack-ui/src/components/TableView/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
* SPDX-License-Identifier: Apache-2.0
*/

import clsx from 'clsx';
import type { PropsWithChildren } from 'react';

import classes from './TableView.module.scss';

interface Props {
description?: string;
className?: string;
}

export function TableView({ description, children }: PropsWithChildren<Props>) {
export function TableView({ description, className, children }: PropsWithChildren<Props>) {
return (
<div className={classes.root}>
<div className={clsx(classes.root, className)}>
{description && <p className={classes.description}>{description}</p>}

<div className={classes.table}>{children}</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@

.root {
margin: -$spacing-03;
display: flex;
justify-content: flex-end;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { DataTableHeader } from '@carbon/react';
import {
DataTable,
DataTableSkeleton,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@carbon/react';
import type { ReactNode } from 'react';

import { useTableSearch } from '#hooks/useTableSearch.ts';

import { TableView } from './TableView.tsx';
import { TableViewToolbar } from './TableViewToolbar.tsx';

interface Props<T> {
headers: (DataTableHeader & { className?: string })[];
entries: T[];
searchFields: (keyof T)[];
toolbarButton: ReactNode;
isPending: boolean;
description?: string;
emptyText?: ReactNode;
className?: string;
}

export function TableViewWithSearch<T extends { id: string }>({
headers,
entries,
searchFields,
toolbarButton,
isPending,
description,
emptyText = 'No results found.',
className,
}: Props<T>) {
const { items: rows, onSearch } = useTableSearch({ entries, fields: searchFields });

const columnCount = headers.length;
const hasRows = rows.length > 0;

return (
<TableView description={description} className={className}>
<DataTable headers={headers} rows={rows}>
{({ rows, getTableProps, getHeaderProps, getRowProps }) => (
<>
<TableViewToolbar searchProps={{ onChange: onSearch, disabled: isPending }} button={toolbarButton} />

{isPending ? (
<DataTableSkeleton headers={headers} columnCount={columnCount} showToolbar={false} showHeader={false} />
) : (
<Table {...getTableProps()}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader {...getHeaderProps({ header })} key={header.key} className={header.className}>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>

<TableBody>
{hasRows ? (
rows.map((row) => (
<TableRow {...getRowProps({ row })} key={row.id}>
{row.cells.map((cell) => {
const header = headers.find(({ key }) => key === cell.info.header);

return (
<TableCell key={cell.id} className={header?.className}>
{cell.value}
</TableCell>
);
})}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columnCount}>{emptyText}</TableCell>
</TableRow>
)}
</TableBody>
</Table>
)}
</>
)}
</DataTable>
</TableView>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { NoItemsMessage } from '#components/NoItemsMessage/NoItemsMessage.tsx';
import { useAgentSecrets } from '#modules/runs/contexts/agent-secrets/index.ts';
import { SecretCard } from '#modules/runs/secrets/SecretCard.tsx';
import { SecretCard } from '#modules/runs/secrets/components/SecretCard.tsx';

import classes from './AgentSecrets.module.scss';

Expand Down
66 changes: 19 additions & 47 deletions apps/agentstack-ui/src/modules/connectors/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,70 +7,42 @@ import { api } from '#api/index.ts';
import { ensureData } from '#api/utils.ts';
import { BASE_URL } from '#utils/constants.ts';

import type { CreateConnectorRequest, ListConnectorsResponse } from './types';
import type {
ConnectConnectorPath,
CreateConnectorRequest,
DeleteConnectorPath,
DisconnectConnectorPath,
} from './types';

export async function createConnector(body: CreateConnectorRequest) {
const response = await api.POST('/api/v1/connectors', { body });
return ensureData(response);
}

export async function deleteConnector(connectorId: string) {
const response = await api.DELETE('/api/v1/connectors/{connector_id}', {
params: { path: { connector_id: connectorId } },
});
return ensureData(response);
}

export async function disconnectConnector(connectorId: string) {
const response = await api.POST('/api/v1/connectors/{connector_id}/disconnect', {
params: { path: { connector_id: connectorId } },
});
return ensureData(response);
}

export async function connectConnector(connectorId: string) {
export async function connectConnector(path: ConnectConnectorPath) {
const response = await api.POST('/api/v1/connectors/{connector_id}/connect', {
params: { path: { connector_id: connectorId } },
body: {
redirect_url: `${BASE_URL}/oauth-callback`,
},
params: { path },
body: { redirect_url: `${BASE_URL}/oauth-callback` },
});

return ensureData(response) as AuthRequired;
return ensureData(response);
}

interface AuthRequired {
id: string;
url: string;
state: 'auth_required';
auth_request: {
authorization_endpoint: string;
type: string;
};
}
export async function disconnectConnector(path: DisconnectConnectorPath) {
const response = await api.POST('/api/v1/connectors/{connector_id}/disconnect', { params: { path } });

// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Connected {
id: string;
url: string;
state: 'connected';
return ensureData(response);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Disconnected {
id: string;
url: string;
state: 'disconnected';
}
export async function deleteConnector(path: DeleteConnectorPath) {
const response = await api.DELETE('/api/v1/connectors/{connector_id}', { params: { path } });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to implement all api calls in the SDK and use them from there from now on. But we can leave that for #1692 .


// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Created {
id: string;
url: string;
state: 'created';
return ensureData(response);
}

export async function listConnectors(): Promise<ListConnectorsResponse | undefined> {
const response = await api.GET('/api/v1/connectors', {});
export async function listConnectors() {
const response = await api.GET('/api/v1/connectors');

return ensureData(response);
}
2 changes: 1 addition & 1 deletion apps/agentstack-ui/src/modules/connectors/api/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
*/

export const connectorKeys = {
all: () => ['oauth-connectors'] as const,
all: () => ['connectors'] as const,
list: () => [...connectorKeys.all(), 'list'] as const,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import { useMutation } from '@tanstack/react-query';

import { connectConnector } from '..';
import { connectorKeys } from '../keys';

export function useConnectConnector() {
const mutation = useMutation({
mutationFn: connectConnector,
meta: {
invalidates: [connectorKeys.list()],
errorToast: {
title: 'Failed to connect service.',
includeErrorMessage: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import { useMutation } from '@tanstack/react-query';
import { createConnector } from '..';
import { connectorKeys } from '../keys';

export function useCreateConnector() {
interface Props {
onSuccess?: () => void;
}

export function useCreateConnector({ onSuccess }: Props = {}) {
const mutation = useMutation({
mutationFn: createConnector,
onSuccess,
meta: {
invalidates: [connectorKeys.list()],
errorToast: {
Expand Down
13 changes: 11 additions & 2 deletions apps/agentstack-ui/src/modules/connectors/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type { ApiRequest, ApiResponse } from '#@types/utils.ts';
import type { ApiPath, ApiRequest, ApiResponse } from '#@types/utils.ts';

export type CreateConnectorRequest = ApiRequest<'/api/v1/connectors'>;
export type ListConnectorsResponse = ApiResponse<'/api/v1/connectors', 'get', 'application/json', 200>;

export type ConnectConnectorPath = ApiPath<'/api/v1/connectors/{connector_id}/connect', 'post'>;

export type DisconnectConnectorPath = ApiPath<'/api/v1/connectors/{connector_id}/disconnect', 'post'>;

export type DeleteConnectorPath = ApiPath<'/api/v1/connectors/{connector_id}', 'delete'>;

export type ListConnectorsResponse = ApiResponse<'/api/v1/connectors'>;

export type Connector = ListConnectorsResponse['items'][number];

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
* SPDX-License-Identifier: Apache-2.0
*/

.stack {
display: flex;
flex-direction: column;
row-gap: $spacing-05;
}
Loading