Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions config-ui/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as apiKey from './api-key';
import * as auth from './auth';
import * as blueprint from './blueprint';
import * as connection from './connection';
import * as org from './org';
import * as pipeline from './pipeline';
import plugin from './plugin';
import * as project from './project';
Expand All @@ -39,6 +40,7 @@ export const API = {
auth,
blueprint,
connection,
org,
pipeline,
plugin,
project,
Expand Down
43 changes: 43 additions & 0 deletions config-ui/src/api/org/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import { request } from '@/utils';

// The org plugin stores teams, users and their account mapping as whole CSV files:
// every PUT replaces the entire table (deleteAll + save), so the UI always sends
// back the complete set of rows rather than a delta.
const upload = (path: string, csv: string, filename: string) => {
const data = new FormData();
data.append('file', new Blob([csv], { type: 'text/csv' }), filename);
return request(path, { method: 'put', data });
};

export const getTeams = (): Promise<string> => request('/plugins/org/teams.csv');

export const updateTeams = (csv: string) => upload('/plugins/org/teams.csv', csv, 'teams.csv');

export const getUsers = (): Promise<string> => request('/plugins/org/users.csv');

export const updateUsers = (csv: string) => upload('/plugins/org/users.csv', csv, 'users.csv');

// Returns every known account (one per connected tool identity) with the UserId it
// is currently mapped to, not only the accounts that already have one.
export const getUserAccountMapping = (): Promise<string> => request('/plugins/org/user_account_mapping.csv');

export const updateUserAccountMapping = (csv: string) =>
upload('/plugins/org/user_account_mapping.csv', csv, 'user_account_mapping.csv');
5 changes: 5 additions & 0 deletions config-ui/src/app/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
Pipelines,
Pipeline,
ApiKeys,
Team,
NotFound,
} from '@/routes';

Expand Down Expand Up @@ -112,6 +113,10 @@ export const router = createBrowserRouter([
},
],
},
{
path: 'teams',
element: <Team />,
},
{
path: 'keys',
element: <ApiKeys />,
Expand Down
1 change: 1 addition & 0 deletions config-ui/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export * from './not-found';
export * from './onboard';
export * from './pipeline';
export * from './project';
export * from './team';
6 changes: 6 additions & 0 deletions config-ui/src/routes/layout/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
ProjectOutlined,
ExperimentOutlined,
KeyOutlined,
TeamOutlined,
DashboardOutlined,
FileSearchOutlined,
ApiOutlined,
Expand Down Expand Up @@ -66,6 +67,11 @@ export const menuItems: MenuItem[] = [
},
],
},
{
key: `${PATH_PREFIX}/teams`,
label: 'Teams',
icon: <TeamOutlined />,
},
{
key: `${PATH_PREFIX}/keys`,
label: 'API Keys',
Expand Down
122 changes: 122 additions & 0 deletions config-ui/src/routes/team/editable-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import { DeleteOutlined } from '@ant-design/icons';
import { Table, Input, Select, Button, Tooltip } from 'antd';

export type EditableRow = Record<string, string> & { _key: string };

export type EditableField = {
key: string;
title: string;
type?: 'text' | 'select' | 'multiple';
width?: number;
placeholder?: string;
options?: Array<{ label: string; value: string }>;
// Multi-value fields are stored in the CSV as a single delimited cell.
separator?: string;
editable?: boolean;
invalid?: (row: EditableRow, rows: EditableRow[]) => string | undefined;
};

interface Props {
fields: EditableField[];
rows: EditableRow[];
onChange: (rows: EditableRow[]) => void;
removable?: boolean;
emptyText?: string;
}

export const EditableTable = ({ fields, rows, onChange, removable = true, emptyText }: Props) => {
const update = (key: string, field: string, value: string) =>
onChange(rows.map((row) => (row._key === key ? { ...row, [field]: value } : row)));

const columns = fields.map((field) => ({
title: field.title,
dataIndex: field.key,
width: field.width,
render: (value: string, row: EditableRow) => {
if (field.editable === false) {
return value || <span style={{ color: '#9ca3af' }}>—</span>;
}

if (field.type === 'select' || field.type === 'multiple') {
const separator = field.separator ?? ';';
const multiple = field.type === 'multiple';
return (
<Select
style={{ width: '100%' }}
mode={multiple ? 'multiple' : undefined}
allowClear
showSearch
optionFilterProp="label"
placeholder={field.placeholder}
options={field.options}
value={multiple ? (value ? value.split(separator).filter(Boolean) : []) : value || undefined}
onChange={(next) =>
update(
row._key,
field.key,
multiple ? ((next as string[]) ?? []).join(separator) : ((next as string) ?? ''),
)
}
/>
);
}

const error = field.invalid?.(row, rows);
return (
<Tooltip title={error} open={error ? undefined : false}>
<Input
status={error ? 'error' : undefined}
placeholder={field.placeholder}
value={value}
onChange={(e) => update(row._key, field.key, e.target.value)}
/>
</Tooltip>
);
},
}));

if (removable) {
columns.push({
title: '',
dataIndex: '_actions',
width: 50,
render: (_: string, row: EditableRow) => (
<Button
type="text"
icon={<DeleteOutlined />}
onClick={() => onChange(rows.filter((it) => it._key !== row._key))}
/>
),
});
}

return (
<Table
rowKey="_key"
size="small"
columns={columns}
dataSource={rows}
pagination={false}
locale={{ emptyText }}
scroll={{ x: true }}
/>
);
};
19 changes: 19 additions & 0 deletions config-ui/src/routes/team/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

export * from './team';
Loading