diff --git a/ui/src/DataProvider.tsx b/ui/src/DataProvider.tsx index ddbb5475a..241085777 100644 --- a/ui/src/DataProvider.tsx +++ b/ui/src/DataProvider.tsx @@ -1,6 +1,12 @@ import { goOidcAgentAuthProvider } from "./providers/AuthProvider"; import simpleRestProvider from "ra-data-simple-rest"; import { fetchUtils } from "react-admin"; +import { + DeleteParams, + GetListParams, + GetListResult, + RaRecord, +} from "ra-core/dist/cjs/types"; const fetchJson = (url: string, options: any = {}) => { // Includes the encrypted session cookie in requests to the API @@ -25,8 +31,56 @@ const baseDataProvider = simpleRestProvider( fetchJson, "X-Total-Count", ); + +function rewriteNestedResource( + resource: string, + params: { id?: any; meta?: any }, +) { + const resourceParts = resource.split("/"); + if (resourceParts.length > 1) { + // to deal with nested resources + let ids: any[] | undefined = undefined; + if (params.id !== undefined) { + ids = [...params.id]; + } else if (params.meta?.ids !== undefined) { + ids = [...params.meta.ids]; + } + if (ids === undefined) { + throw new Error("meta.ids or id is required to access nested resources"); + } + + if (ids.length < resourceParts.length - 1) { + throw new Error( + `meta.ids should contain at least ${resourceParts.length - 1} elements`, + ); + } + + const parts = [resourceParts[0]]; + for (let i = 0; i < resourceParts.length - 1; i++) { + parts.push(ids[i]); + parts.push(resourceParts[i + 1]); + } + resource = parts.join("/"); + + if (params.id !== undefined) { + params.id = params.id.pop(); + } + } + return resource; +} + +async function deleteResource(resource: string, params: DeleteParams) { + resource = rewriteNestedResource(resource, params); + let result = await baseDataProvider.delete(resource, params); + if (params.meta?.importer) { + result.data = params.meta.importer(result.data); + } + return result; +} + export const dataProvider = { ...baseDataProvider, + getFlag: (name: string) => { return fetchJson(`${backend}/api/fflags/${name}`).then( (response) => response, @@ -37,4 +91,37 @@ export const dataProvider = { [index: string]: boolean; }; }, + + delete: deleteResource, + + deleteMany: function (resource: string, params: any) { + return Promise.all( + params.ids.map(function (id: any) { + return deleteResource(resource, { ...params, id: id }); + }), + ).then(function (responses) { + return { + data: responses.map(function (a) { + return a.data.id; + }), + }; + }); + }, + + getList: async ( + resource: string, + params: GetListParams, + ): Promise> => { + resource = rewriteNestedResource(resource, params); + + let result = await baseDataProvider.getList(resource, params); + + if (params.meta?.importer) { + result.data = result.data.map((record: any) => { + return params.meta.importer(record); + }); + } + + return result; + }, }; diff --git a/ui/src/pages/Organizations.tsx b/ui/src/pages/Organizations.tsx index 2ab78786d..8f8a0a874 100644 --- a/ui/src/pages/Organizations.tsx +++ b/ui/src/pages/Organizations.tsx @@ -13,6 +13,7 @@ import { SimpleForm, TextInput, } from "react-admin"; +import { UserOrganizationList } from "./UserOrganizations"; const OrganizationListBulkActions = () => ( @@ -45,6 +46,8 @@ export const OrganizationShow = () => ( +

User Membership

+ ); diff --git a/ui/src/pages/UserOrganizations.tsx b/ui/src/pages/UserOrganizations.tsx new file mode 100644 index 000000000..d19cf40c4 --- /dev/null +++ b/ui/src/pages/UserOrganizations.tsx @@ -0,0 +1,38 @@ +import { + BulkDeleteButton, + BulkExportButton, + Datagrid, + List, + TextField, +} from "react-admin"; +import { useParams } from "react-router-dom"; +import React, { Fragment } from "react"; + +function importer(record: any) { + record.id = [record.organization_id, record.user_id]; + return record; +} + +export const UserOrganizationList = () => { + const { id } = useParams(); + + return ( + + + + +
+ } + > + + + + + ); +};