mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
console: support for remote relationship permission
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10193 GitOrigin-RevId: ed90991c7919e50c51adcba323f57d6446e9679c
This commit is contained in:
parent
de8d130a47
commit
207dabec8d
@ -38,8 +38,8 @@ export const getTableDisplayName = (table: Table): string => {
|
||||
return JSON.stringify(table);
|
||||
};
|
||||
|
||||
export function isNotRemoteRelationship(
|
||||
export function isNotRemoteSchemaRelationship(
|
||||
relationship: Relationship
|
||||
): relationship is LocalRelationship | RemoteDatabaseRelationship {
|
||||
return relationship.relationshipType !== 'Remote';
|
||||
return relationship.type !== 'remoteSchemaRelationship';
|
||||
}
|
||||
|
@ -42,11 +42,13 @@ export const AggregationSection: React.FC<AggregationProps> = ({
|
||||
setValue('aggregationEnabled', !enabled);
|
||||
setValue(
|
||||
'query_root_fields',
|
||||
queryRootFields.filter((field: string) => field !== 'select_aggregate')
|
||||
(queryRootFields ?? []).filter(
|
||||
(field: string) => field !== 'select_aggregate'
|
||||
)
|
||||
);
|
||||
setValue(
|
||||
'subscription_root_fields',
|
||||
subscriptionRootFields.filter(
|
||||
(subscriptionRootFields ?? []).filter(
|
||||
(field: string) => field !== 'select_aggregate'
|
||||
)
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Table } from '../../../../hasura-metadata-types';
|
||||
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { RowPermissionsInput } from './components';
|
||||
import { RowPermissionsInput, TableToLoad } from './components';
|
||||
import { usePermissionTables } from './hooks/usePermissionTables';
|
||||
import { usePermissionComparators } from './hooks/usePermissionComparators';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
@ -25,7 +25,9 @@ export const RowPermissionBuilder = ({
|
||||
// this value will always be 'filter' or 'check' depending on the query type
|
||||
|
||||
const value = watch(permissionsKey);
|
||||
const [tablesToLoad, setTablesToLoad] = useState<Table[]>([table]);
|
||||
const [tablesToLoad, setTablesToLoad] = useState<TableToLoad>([
|
||||
{ table, source: dataSourceName },
|
||||
]);
|
||||
|
||||
const { tables, isLoading } = usePermissionTables({
|
||||
dataSourceName,
|
||||
|
@ -95,7 +95,7 @@ export const Operator = ({
|
||||
))}
|
||||
</optgroup>
|
||||
) : null}
|
||||
{relationships?.length ? (
|
||||
{relationships.length ? (
|
||||
<optgroup label="Relationships">
|
||||
{relationships.map((item, index) => (
|
||||
<option
|
||||
|
@ -723,8 +723,11 @@ export const JsonbColumns: StoryObj<typeof RowPermissionsInput> = {
|
||||
dataSourceName: 'default',
|
||||
tablesToLoad: [
|
||||
{
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
source: 'default',
|
||||
table: {
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -775,8 +778,11 @@ export const JsonbColumnsHasKeys: StoryObj<typeof RowPermissionsInput> = {
|
||||
dataSourceName: 'default',
|
||||
tablesToLoad: [
|
||||
{
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
source: 'default',
|
||||
table: {
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -811,8 +817,11 @@ export const StringColumns: StoryObj<typeof RowPermissionsInput> = {
|
||||
dataSourceName: 'default',
|
||||
tablesToLoad: [
|
||||
{
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
source: 'default',
|
||||
table: {
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -867,8 +876,11 @@ export const NumberColumns: StoryObj<typeof RowPermissionsInput> = {
|
||||
dataSourceName: 'default',
|
||||
tablesToLoad: [
|
||||
{
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
source: 'default',
|
||||
table: {
|
||||
name: 'Stuff',
|
||||
schema: 'public',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -1049,7 +1061,12 @@ export const RemoteRelationships: StoryObj<typeof RowPermissionsInput> = {
|
||||
const [permissions, setPermissions] = useState<Permissions>({});
|
||||
const { tables } = usePermissionTables({
|
||||
dataSourceName: 'OhMy',
|
||||
tablesToLoad: [['Chinook', 'Artist']],
|
||||
tablesToLoad: [
|
||||
{
|
||||
source: 'Chinook',
|
||||
table: ['Chinook', 'Artist'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const comparators = usePermissionComparators();
|
||||
@ -1093,7 +1110,12 @@ export const NestedObjects: StoryObj<typeof RowPermissionsInput> = {
|
||||
render: args => {
|
||||
const { tables } = usePermissionTables({
|
||||
dataSourceName: 'M',
|
||||
tablesToLoad: [['students']],
|
||||
tablesToLoad: [
|
||||
{
|
||||
source: 'M',
|
||||
table: ['students'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const comparators = usePermissionComparators();
|
||||
@ -1131,7 +1153,12 @@ export const NestedObjectsInitiallyEmpty: StoryObj<typeof RowPermissionsInput> =
|
||||
render: args => {
|
||||
const { tables } = usePermissionTables({
|
||||
dataSourceName: 'M',
|
||||
tablesToLoad: [['students']],
|
||||
tablesToLoad: [
|
||||
{
|
||||
source: 'M',
|
||||
table: ['students'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const comparators = usePermissionComparators();
|
||||
@ -1190,7 +1217,12 @@ export const NestedObjectsAnd: StoryObj<typeof RowPermissionsInput> = {
|
||||
render: args => {
|
||||
const { tables } = usePermissionTables({
|
||||
dataSourceName: 'M',
|
||||
tablesToLoad: [['students']],
|
||||
tablesToLoad: [
|
||||
{
|
||||
source: 'M',
|
||||
table: ['students'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const comparators = usePermissionComparators();
|
||||
@ -1227,7 +1259,12 @@ export const NestedObjectsOr: StoryObj<typeof RowPermissionsInput> = {
|
||||
render: args => {
|
||||
const { tables } = usePermissionTables({
|
||||
dataSourceName: 'M',
|
||||
tablesToLoad: [['students']],
|
||||
tablesToLoad: [
|
||||
{
|
||||
source: 'M',
|
||||
table: ['students'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const comparators = usePermissionComparators();
|
||||
|
@ -30,23 +30,35 @@ export const TableProvider = ({
|
||||
const [comparator, setComparator] = useState<string | undefined>();
|
||||
const [columns, setColumns] = useState<Columns>([]);
|
||||
const [relationships, setRelationships] = useState<Relationships>([]);
|
||||
const { tables } = useContext(rootTableContext);
|
||||
const { tables, rootTable } = useContext(rootTableContext);
|
||||
const { loadRelationships } = useContext(rowPermissionsContext);
|
||||
const { table: closestTableName } = useContext(tableContext);
|
||||
const closestTable = tables.find(t =>
|
||||
areTablesEqual(t.table, closestTableName)
|
||||
);
|
||||
|
||||
const supportedSources = tables
|
||||
.map(i => i.dataSource)
|
||||
.filter(s => s?.kind === 'postgres')
|
||||
.map(s => s?.name);
|
||||
|
||||
// Stringify values to get a stable value for useEffect
|
||||
const stringifiedTable = JSON.stringify(table);
|
||||
const stringifiedTables = JSON.stringify(tables);
|
||||
|
||||
useEffect(() => {
|
||||
const foundTable = tables.find(t => areTablesEqual(t.table, table));
|
||||
if (foundTable) {
|
||||
setColumns(foundTable.columns);
|
||||
if (foundTable?.dataSource?.name !== rootTable?.dataSource?.name) return;
|
||||
setRelationships(
|
||||
foundTable.relationships.filter(
|
||||
relationship => relationship.type === 'localRelationship'
|
||||
)
|
||||
foundTable.relationships.filter(rel => {
|
||||
return (
|
||||
rel.type === 'localRelationship' ||
|
||||
(rel.type !== 'remoteSchemaRelationship' &&
|
||||
supportedSources?.includes(rel.definition.toSource))
|
||||
);
|
||||
})
|
||||
);
|
||||
// Load initial related tables' columns
|
||||
loadRelationships?.(foundTable.relationships);
|
||||
|
@ -80,3 +80,5 @@ export type TableContext = {
|
||||
relationships: Relationships;
|
||||
setRelationships: (relationships: Relationships) => void;
|
||||
};
|
||||
|
||||
export type TableToLoad = { source: string; table: Table }[];
|
||||
|
@ -1,20 +1,18 @@
|
||||
import { getAllTableRelationships } from '../../../../../DatabaseRelationships/utils/tableRelationships';
|
||||
import { useTablesWithColumns } from './useTablesWithColumns';
|
||||
import { useSources } from '../../../../../MetadataAPI';
|
||||
import { Tables } from '../components';
|
||||
import { TableToLoad, Tables } from '../components';
|
||||
import { useAllSuggestedRelationships } from '../../../../../DatabaseRelationships/components/SuggestedRelationships/hooks/useAllSuggestedRelationships';
|
||||
import { Table } from '../../../../../hasura-metadata-types';
|
||||
|
||||
export const usePermissionTables = ({
|
||||
dataSourceName,
|
||||
tablesToLoad,
|
||||
}: {
|
||||
dataSourceName: string;
|
||||
tablesToLoad: Table[];
|
||||
tablesToLoad: TableToLoad;
|
||||
}): { isLoading: boolean; tables: Tables | null } => {
|
||||
const { data: sources, isLoading: isLoadingSources } = useSources();
|
||||
const { data: tables, isLoading: isLoadingTables } = useTablesWithColumns({
|
||||
dataSourceName,
|
||||
tablesToLoad,
|
||||
});
|
||||
|
||||
@ -31,10 +29,10 @@ export const usePermissionTables = ({
|
||||
return {
|
||||
isLoading: false,
|
||||
tables:
|
||||
tables?.map(({ metadataTable, columns }) => {
|
||||
tables?.map(({ metadataTable, columns, sourceName }) => {
|
||||
return {
|
||||
table: metadataTable.table,
|
||||
dataSource: sources?.find(source => source.name === dataSourceName),
|
||||
dataSource: sources?.find(source => source.name === sourceName),
|
||||
relationships: getAllTableRelationships(
|
||||
metadataTable,
|
||||
dataSourceName,
|
||||
|
@ -4,30 +4,25 @@ import {
|
||||
exportMetadata,
|
||||
TableColumn,
|
||||
} from '../../../../../DataSource';
|
||||
import { MetadataTable, Table } from '../../../../../hasura-metadata-types';
|
||||
import { MetadataTable } from '../../../../../hasura-metadata-types';
|
||||
import { useHttpClient } from '../../../../../Network';
|
||||
import { areTablesEqual } from '../../../../../hasura-metadata-api';
|
||||
|
||||
const tablesQueryKey = (dataSourceName: string) => [
|
||||
'tables-with-columns',
|
||||
dataSourceName,
|
||||
];
|
||||
import { TableToLoad } from '../components';
|
||||
|
||||
export type TableWithColumns = {
|
||||
metadataTable: MetadataTable;
|
||||
columns: TableColumn[];
|
||||
sourceName: string;
|
||||
};
|
||||
|
||||
export const useTablesWithColumns = ({
|
||||
dataSourceName,
|
||||
tablesToLoad,
|
||||
}: {
|
||||
dataSourceName: string;
|
||||
tablesToLoad: Table[];
|
||||
tablesToLoad: TableToLoad;
|
||||
}) => {
|
||||
const httpClient = useHttpClient();
|
||||
return useQuery<TableWithColumns[], Error>({
|
||||
queryKey: [...tablesQueryKey(dataSourceName), tablesToLoad],
|
||||
queryKey: [tablesToLoad],
|
||||
queryFn: async () => {
|
||||
const { metadata } = await exportMetadata({
|
||||
httpClient,
|
||||
@ -35,24 +30,29 @@ export const useTablesWithColumns = ({
|
||||
|
||||
if (!metadata) throw Error('metadata not found');
|
||||
|
||||
const currentMetadataSource = metadata.sources?.find(
|
||||
source => source.name === dataSourceName
|
||||
);
|
||||
|
||||
if (!currentMetadataSource)
|
||||
throw Error(`useTables.metadataSource not found`);
|
||||
|
||||
const result: TableWithColumns[] = [];
|
||||
|
||||
for (const metadataTable of currentMetadataSource.tables) {
|
||||
if (tablesToLoad.find(t => areTablesEqual(metadataTable.table, t))) {
|
||||
const columns = await DataSource(httpClient).getTableColumns({
|
||||
dataSourceName,
|
||||
table: metadataTable.table,
|
||||
});
|
||||
result.push({ metadataTable, columns });
|
||||
} else {
|
||||
result.push({ metadataTable, columns: [] });
|
||||
for (const source of metadata.sources) {
|
||||
for (const metadataTable of source.tables) {
|
||||
if (
|
||||
tablesToLoad.find(
|
||||
t =>
|
||||
areTablesEqual(metadataTable.table, t.table) &&
|
||||
source.name === t?.source
|
||||
)
|
||||
) {
|
||||
const columns = await DataSource(httpClient).getTableColumns({
|
||||
dataSourceName: source.name,
|
||||
table: metadataTable.table,
|
||||
});
|
||||
result.push({ metadataTable, columns, sourceName: source.name });
|
||||
} else {
|
||||
result.push({
|
||||
metadataTable,
|
||||
columns: [],
|
||||
sourceName: source.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { isNotRemoteRelationship } from '../../../../../DatabaseRelationships/utils/helpers';
|
||||
import { Table } from '../../../../../hasura-metadata-types';
|
||||
import { Relationships } from '../components';
|
||||
import { isNotRemoteSchemaRelationship } from '../../../../../DatabaseRelationships/utils/helpers';
|
||||
import { Relationships, TableToLoad } from '../components';
|
||||
|
||||
/**
|
||||
* Get the relationship tables that are not already in the tablesToLoad array
|
||||
@ -10,9 +9,9 @@ export function getNewTablesToLoad({
|
||||
tablesToLoad,
|
||||
}: {
|
||||
relationships: Relationships;
|
||||
tablesToLoad: Table[];
|
||||
}): Table[] {
|
||||
const relatedTables = relationships.filter(isNotRemoteRelationship);
|
||||
tablesToLoad: TableToLoad;
|
||||
}): TableToLoad {
|
||||
const relatedTables = relationships.filter(isNotRemoteSchemaRelationship);
|
||||
|
||||
// Create a Set of existing tables for faster lookups
|
||||
const existingTablesSet = new Set<string>(
|
||||
@ -23,11 +22,24 @@ export function getNewTablesToLoad({
|
||||
const tablesToAddSet = new Set<string>();
|
||||
|
||||
relatedTables.forEach(relationship => {
|
||||
const toTableString = JSON.stringify(relationship.definition.toTable);
|
||||
|
||||
let tableObject = undefined;
|
||||
if (
|
||||
'toSource' in relationship.definition &&
|
||||
typeof relationship.definition.toTable === 'object'
|
||||
) {
|
||||
tableObject = JSON.stringify({
|
||||
source: relationship.definition.toSource,
|
||||
table: relationship.definition.toTable,
|
||||
});
|
||||
} else {
|
||||
tableObject = JSON.stringify({
|
||||
table: relationship.definition.toTable,
|
||||
source: relationship.fromSource,
|
||||
});
|
||||
}
|
||||
// Check if the toTable is not in the existing tables
|
||||
if (!existingTablesSet.has(toTableString)) {
|
||||
tablesToAddSet.add(toTableString);
|
||||
if (!existingTablesSet.has(tableObject)) {
|
||||
tablesToAddSet.add(tableObject);
|
||||
}
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user