mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
console: add reload all databases
checkbox to the metadata settings page
Closes: https://github.com/hasura/graphql-engine/issues/7146 ### Description This PR adds a `reload all databases` check box along `with` the `reload metadata` check box to reload all databases (inconsistent databases also). ![Screenshot from 2021-07-07 16-51-49](https://user-images.githubusercontent.com/68095256/124750932-af31a000-df43-11eb-88d9-b6585214a188.png) ### Affected components - [x] Console ### Steps to test and verify Use this for your docker image: https://235669-308610159-gh.circle-artifacts.com/0/server/image.tar 1. Go to the metadata setting page 2. If `reload all databases` check box is checked: It reloads everything by sending * in the API 3. If `reloading all databases` check box is not checked: It reloads inconsistent DB and if no inconsistent then it passes an empty string https://github.com/hasura/graphql-engine-mono/pull/1703 GitOrigin-RevId: 2b6f029a17d40a43f5bce3680d5681c97433d78c
This commit is contained in:
parent
cdfb71c1da
commit
b1013e8d6c
@ -15,7 +15,8 @@
|
||||
- server: log all HTTP errors in remote schema calls as `remote-schema-error` with details
|
||||
- server: For BigQuery, make `global_select_limit` configuration optional with a default value of
|
||||
`1000`
|
||||
|
||||
- console: add `reload all databases` checkbox to the metadata settings page
|
||||
- console: add schema sharing
|
||||
|
||||
## v2.0.1
|
||||
|
||||
|
@ -34,8 +34,8 @@ const MetadataOptions = props => {
|
||||
<div key="meta_data_1" className={styles.intro_note}>
|
||||
<h4>Reload metadata</h4>
|
||||
<div className={styles.content_width}>
|
||||
Refresh Hasura metadata, typically required if you have changed the
|
||||
underlying database or if you have updated your remote schemas.
|
||||
Refresh Hasura metadata, typically required if you have changes in
|
||||
the underlying databases or if you have updated your remote schemas.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import {
|
||||
showSuccessNotification,
|
||||
showErrorNotification,
|
||||
@ -10,12 +10,15 @@ import Tooltip from '../../../Common/Tooltip/Tooltip';
|
||||
import metaDataStyles from '../Settings.scss';
|
||||
import { reloadMetadata } from '../../../../metadata/actions';
|
||||
|
||||
import styles from '../Settings.scss';
|
||||
|
||||
class ReloadMetadata extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isReloading: false,
|
||||
shouldReloadRemoteSchemas: props.shouldReloadRemoteSchemas || false,
|
||||
shouldReloadRemoteSchemas: false,
|
||||
shouldReloadAllSources: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -25,6 +28,12 @@ class ReloadMetadata extends Component {
|
||||
}));
|
||||
};
|
||||
|
||||
toggleShouldReloadAllSources = () => {
|
||||
this.setState(state => ({
|
||||
shouldReloadAllSources: !state.shouldReloadAllSources,
|
||||
}));
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatch,
|
||||
@ -32,7 +41,11 @@ class ReloadMetadata extends Component {
|
||||
tooltipStyle,
|
||||
showReloadRemoteSchemas = true,
|
||||
} = this.props;
|
||||
const { isReloading, shouldReloadRemoteSchemas } = this.state;
|
||||
const {
|
||||
isReloading,
|
||||
shouldReloadRemoteSchemas,
|
||||
shouldReloadAllSources,
|
||||
} = this.state;
|
||||
|
||||
const reloadMetadataAndLoadInconsistentMetadata = e => {
|
||||
e.preventDefault();
|
||||
@ -42,6 +55,7 @@ class ReloadMetadata extends Component {
|
||||
dispatch(
|
||||
reloadMetadata(
|
||||
shouldReloadRemoteSchemas,
|
||||
shouldReloadAllSources,
|
||||
() => {
|
||||
dispatch(showSuccessNotification('Metadata reloaded'));
|
||||
this.setState({ isReloading: false });
|
||||
@ -90,6 +104,22 @@ class ReloadMetadata extends Component {
|
||||
<Tooltip message="Check this if you have inconsistent remote schemas or if your remote schema has changed." />
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<label
|
||||
className={`${metaDataStyles.cursorPointer} ${metaDataStyles.add_mar_right_small} ${metaDataStyles.add_mar_left_small} ${styles.metaDataMargin}`}
|
||||
disabled={this.state.isReloading}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={this.toggleShouldReloadAllSources}
|
||||
checked={shouldReloadAllSources}
|
||||
readOnly
|
||||
className={`${metaDataStyles.add_mar_right_small} ${metaDataStyles.cursorPointer}`}
|
||||
/>
|
||||
Reload all databases
|
||||
</label>
|
||||
<Tooltip message="Check this if you have inconsistent databases." />
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -109,4 +109,8 @@
|
||||
|
||||
.roleNameContainer{
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.metaDataMargin {
|
||||
margin-left: 15px;
|
||||
}
|
@ -1,6 +1,115 @@
|
||||
import { HasuraMetadataV3 } from '../../types';
|
||||
import { APILimitInputType } from '../../utils';
|
||||
|
||||
export const inconsistentSourceObjects = [
|
||||
{
|
||||
definition: 'TestDB',
|
||||
reason: 'Inconsistent object: sql server exception',
|
||||
name: 'source TestDB',
|
||||
type: 'source',
|
||||
},
|
||||
];
|
||||
|
||||
export const inconsistentTableObject = [
|
||||
{
|
||||
definition: {
|
||||
name: 'Table2',
|
||||
schema: 'TestSchema',
|
||||
},
|
||||
name: 'table TestSchema.Table2 in source default',
|
||||
reason: 'Inconsistent object: no such table/view exists in source',
|
||||
type: 'table',
|
||||
},
|
||||
];
|
||||
|
||||
export const inconsistentRelationshipObject = [
|
||||
{
|
||||
definition: {
|
||||
name: 'relationshipName',
|
||||
source: 'default',
|
||||
Comment: null,
|
||||
},
|
||||
name:
|
||||
'object_relation relationshipName in table TestSchema.Table1 in source default',
|
||||
reason: 'Inconsistent object: table "TestSchema.Table2" is not tracked',
|
||||
type: 'object_relation',
|
||||
},
|
||||
];
|
||||
|
||||
export const inconsistentObject = [
|
||||
{
|
||||
definition: 'TestDB',
|
||||
reason: 'Inconsistent object: sql server exception',
|
||||
name: 'source TestDB',
|
||||
type: 'source',
|
||||
},
|
||||
{
|
||||
definition: {
|
||||
name: 'Table2',
|
||||
schema: 'TestSchema',
|
||||
},
|
||||
name: 'table TestSchema.Table2 in source default',
|
||||
reason: 'Inconsistent object: no such table/view exists in source',
|
||||
type: 'table',
|
||||
},
|
||||
{
|
||||
definition: {
|
||||
name: 'relationshipName',
|
||||
source: 'default',
|
||||
Comment: null,
|
||||
},
|
||||
name:
|
||||
'object_relation relationshipName in table TestSchema.Table1 in source default',
|
||||
reason: 'Inconsistent object: table "TestSchema.Table2" is not tracked',
|
||||
type: 'object_relation',
|
||||
},
|
||||
];
|
||||
|
||||
export const inconsistentRemoteSchema = [
|
||||
{
|
||||
name: `remote_schema_permission role permission in remote schema Test1`,
|
||||
reason: `Inconsistent object: remote schema "Test1" does not exis`,
|
||||
type: 'remote_schema_permission',
|
||||
},
|
||||
{
|
||||
name: `remote_schema Test2`,
|
||||
reason: `Inconsistent object: Error in $: key "data" not found`,
|
||||
type: `remote_schema`,
|
||||
},
|
||||
];
|
||||
|
||||
export const inconsistentRemoteRelation = [
|
||||
{
|
||||
definition: {
|
||||
name: 'relation_name',
|
||||
remote_schema: 'Test',
|
||||
source: 'default',
|
||||
},
|
||||
type: 'remote_relationship',
|
||||
},
|
||||
];
|
||||
|
||||
export const multipleInconsistencyRemoteSchema = [
|
||||
{
|
||||
name: `remote_schema_permission role permission in remote schema Test`,
|
||||
reason: `Inconsistent object: remote schema "Test" does not exis`,
|
||||
type: 'remote_schema_permission',
|
||||
},
|
||||
{
|
||||
name: `remote_schema Test`,
|
||||
reason: `Inconsistent object: Error in $: key "data" not found`,
|
||||
type: `remote_schema`,
|
||||
},
|
||||
{
|
||||
definition: {
|
||||
name: 'relation_name',
|
||||
remote_schema: 'Test',
|
||||
source: 'default',
|
||||
},
|
||||
type: 'remote_relationship',
|
||||
},
|
||||
];
|
||||
|
||||
type apiLimitsType = {
|
||||
old_state: HasuraMetadataV3['api_limits'];
|
||||
new_state: {
|
||||
|
59
console/src/metadata/__tests__/metadataUtils.test.ts
Normal file
59
console/src/metadata/__tests__/metadataUtils.test.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import {
|
||||
getRemoteSchemaNameFromInconsistentObjects,
|
||||
getSourceFromInconistentObjects,
|
||||
} from '../utils';
|
||||
import {
|
||||
inconsistentObject,
|
||||
inconsistentRelationshipObject,
|
||||
inconsistentRemoteRelation,
|
||||
inconsistentRemoteSchema,
|
||||
inconsistentSourceObjects,
|
||||
inconsistentTableObject,
|
||||
multipleInconsistencyRemoteSchema,
|
||||
} from './fixtures/input';
|
||||
|
||||
describe('Metadata_Utils_getSourceFromInconistentObjects()', () => {
|
||||
it('should generate an array of soure name for inconsistent DB ', () => {
|
||||
const sourceNameArray = getSourceFromInconistentObjects(
|
||||
inconsistentSourceObjects
|
||||
);
|
||||
expect(sourceNameArray).toEqual(['TestDB']);
|
||||
});
|
||||
it('should generate an array of soure name from inconsistent object due to table deletion ', () => {
|
||||
const sourceNameArray = getSourceFromInconistentObjects(
|
||||
inconsistentTableObject
|
||||
);
|
||||
expect(sourceNameArray).toEqual([]);
|
||||
});
|
||||
it('should generate an array of soure name from inconsistent object due to inconsistent relation ', () => {
|
||||
const sourceNameArray = getSourceFromInconistentObjects(
|
||||
inconsistentRelationshipObject
|
||||
);
|
||||
expect(sourceNameArray).toEqual(['default']);
|
||||
});
|
||||
it('should generate an array of soure name from inconsistent object due to inconsistent relation, DB and table deletion ', () => {
|
||||
const sourceNameArray = getSourceFromInconistentObjects(inconsistentObject);
|
||||
expect(sourceNameArray).toEqual(['TestDB', 'default']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metadata_Utils_getRemoteSchemaNameFromInconsistentObjects()', () => {
|
||||
it('should generate an array of remote schema name for inconsistent remote_schema, remote_schema_permission, remote_schema_relation ', () => {
|
||||
const rsNameArray = getRemoteSchemaNameFromInconsistentObjects(
|
||||
inconsistentRemoteSchema
|
||||
);
|
||||
expect(rsNameArray).toEqual(['Test1', 'Test2']);
|
||||
});
|
||||
it('should generate an array of remote schema name for inconsistent remote_relation ', () => {
|
||||
const rsNameArray = getRemoteSchemaNameFromInconsistentObjects(
|
||||
inconsistentRemoteRelation
|
||||
);
|
||||
expect(rsNameArray).toEqual(['Test']);
|
||||
});
|
||||
it('should generate an array of a single remote schema name for multiple inconsistency in a remote_schema ', () => {
|
||||
const rsNameArray = getRemoteSchemaNameFromInconsistentObjects(
|
||||
multipleInconsistencyRemoteSchema
|
||||
);
|
||||
expect(rsNameArray).toEqual(['Test']);
|
||||
});
|
||||
});
|
@ -27,6 +27,8 @@ import {
|
||||
updateAllowedQueryQuery,
|
||||
allowedQueriesCollection,
|
||||
addAllowedQuery,
|
||||
getSourceFromInconistentObjects,
|
||||
getRemoteSchemaNameFromInconsistentObjects,
|
||||
} from './utils';
|
||||
import {
|
||||
makeMigrationCall,
|
||||
@ -671,19 +673,49 @@ export const loadInconsistentObjects = (
|
||||
reloadConfig: {
|
||||
shouldReloadMetadata?: boolean;
|
||||
shouldReloadRemoteSchemas?: boolean;
|
||||
shouldReloadAllSources?: boolean;
|
||||
},
|
||||
successCb?: () => void,
|
||||
failureCb?: (error: string) => void
|
||||
): Thunk<void, MetadataActions> => {
|
||||
return (dispatch, getState) => {
|
||||
const inconsistentObjectsInMetadata = getState().metadata
|
||||
.inconsistentObjects;
|
||||
|
||||
const inconsistentSources = getSourceFromInconistentObjects(
|
||||
inconsistentObjectsInMetadata
|
||||
);
|
||||
const inconsistentRemoteSchemas = getRemoteSchemaNameFromInconsistentObjects(
|
||||
inconsistentObjectsInMetadata
|
||||
);
|
||||
|
||||
const headers = getState().tables.dataHeaders;
|
||||
const source = getState().tables.currentDataSource;
|
||||
const { shouldReloadMetadata, shouldReloadRemoteSchemas } = reloadConfig;
|
||||
const {
|
||||
shouldReloadMetadata,
|
||||
shouldReloadRemoteSchemas,
|
||||
shouldReloadAllSources,
|
||||
} = reloadConfig;
|
||||
|
||||
let reloadSources: string[] | boolean = [];
|
||||
if (shouldReloadAllSources) {
|
||||
reloadSources = true;
|
||||
} else if (inconsistentSources.length) {
|
||||
reloadSources = inconsistentSources;
|
||||
}
|
||||
|
||||
let reloadRemoteSchemas: string[] | boolean = [];
|
||||
if (shouldReloadRemoteSchemas) {
|
||||
reloadRemoteSchemas = true;
|
||||
} else if (inconsistentRemoteSchemas.length) {
|
||||
reloadRemoteSchemas = inconsistentRemoteSchemas;
|
||||
}
|
||||
|
||||
const loadQuery = shouldReloadMetadata
|
||||
? getReloadCacheAndGetInconsistentObjectsQuery(
|
||||
!!shouldReloadRemoteSchemas,
|
||||
source
|
||||
reloadRemoteSchemas,
|
||||
source,
|
||||
reloadSources
|
||||
)
|
||||
: inconsistentObjectsQuery;
|
||||
|
||||
@ -799,6 +831,7 @@ export const reloadRemoteSchema = (
|
||||
|
||||
export const reloadMetadata = (
|
||||
shouldReloadRemoteSchemas: boolean,
|
||||
shouldReloadAllSources: boolean,
|
||||
successCb: () => void,
|
||||
failureCb: () => void
|
||||
): Thunk<void, MetadataActions> => {
|
||||
@ -808,6 +841,7 @@ export const reloadMetadata = (
|
||||
{
|
||||
shouldReloadMetadata: true,
|
||||
shouldReloadRemoteSchemas,
|
||||
shouldReloadAllSources,
|
||||
},
|
||||
successCb,
|
||||
failureCb
|
||||
|
@ -386,10 +386,14 @@ export const dropInconsistentObjectsQuery = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
export const getReloadMetadataQuery = (shouldReloadRemoteSchemas: boolean) => ({
|
||||
export const getReloadMetadataQuery = (
|
||||
shouldReloadRemoteSchemas: boolean | string[],
|
||||
shouldReloadSources?: boolean | string[]
|
||||
) => ({
|
||||
type: 'reload_metadata',
|
||||
args: {
|
||||
reload_remote_schemas: shouldReloadRemoteSchemas,
|
||||
reload_sources: shouldReloadSources ?? [],
|
||||
reload_remote_schemas: shouldReloadRemoteSchemas ?? [],
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -136,13 +136,14 @@ export const reloadRemoteSchemaCacheAndGetInconsistentObjectsQuery = (
|
||||
};
|
||||
|
||||
export const getReloadCacheAndGetInconsistentObjectsQuery = (
|
||||
shouldReloadRemoteSchemas: boolean,
|
||||
source: string
|
||||
shouldReloadRemoteSchemas: boolean | string[],
|
||||
source: string,
|
||||
shouldReloadSources?: boolean | string[]
|
||||
) => ({
|
||||
type: 'bulk',
|
||||
source,
|
||||
args: [
|
||||
getReloadMetadataQuery(shouldReloadRemoteSchemas),
|
||||
getReloadMetadataQuery(shouldReloadRemoteSchemas, shouldReloadSources),
|
||||
inconsistentObjectsQuery,
|
||||
],
|
||||
});
|
||||
@ -293,3 +294,52 @@ export const isMetadataEmpty = (metadataObject: HasuraMetadataV3) => {
|
||||
export const hasSources = (metadataObject: HasuraMetadataV3) => {
|
||||
return metadataObject?.sources?.length > 0;
|
||||
};
|
||||
|
||||
// NOTE: for a inconsistent object of type "source" the inconsistentObject.definition is the name of the source
|
||||
// for every other inconsistent object if "source" is relevent it will be in inconsistentObject.definition.source
|
||||
|
||||
// getSourceFromInconistentObjects should be used to extract the source from any inconsistent object
|
||||
export const getSourceFromInconistentObjects = (inconsistentObjects: any[]) =>
|
||||
inconsistentObjects
|
||||
.map(
|
||||
inconsistentObject =>
|
||||
(inconsistentObject?.type === 'source' &&
|
||||
inconsistentObject?.definition) ||
|
||||
inconsistentObject?.definition?.source
|
||||
)
|
||||
.filter(sourceName => typeof sourceName === 'string')
|
||||
.filter(
|
||||
(sourceName, index, sourceNameList) =>
|
||||
sourceNameList?.indexOf(sourceName) === index
|
||||
); // to remove duplicate source names
|
||||
|
||||
// NOTE: It can be seen that the `name` field within the object that contains the
|
||||
// information about the inconsistent object for `remote_schema` and `remote_schema_permission`
|
||||
// contains a sentence with complete details of the remote schema(like name, role .etc). In here,
|
||||
// the name of the remote schema always comes at the very end. Since this "HACK" is being
|
||||
// used to fetch the remote schema name, it can become a source of bugs.
|
||||
export const getRemoteSchemaNameFromInconsistentObjects = (
|
||||
inconsistentObjects: any[]
|
||||
) =>
|
||||
inconsistentObjects.reduce((rsNameList, inconsistentObject) => {
|
||||
const inconsistantObjectSplited = inconsistentObject?.name?.split(' ');
|
||||
if (
|
||||
inconsistentObject?.type === 'remote_schema' ||
|
||||
inconsistentObject?.type === 'remote_schema_permission'
|
||||
) {
|
||||
const rsName =
|
||||
inconsistantObjectSplited?.[inconsistantObjectSplited?.length - 1];
|
||||
if (!rsNameList.includes(rsName)) {
|
||||
// to avoid duplicate remote schema name
|
||||
return [...rsNameList, rsName];
|
||||
}
|
||||
} else if (
|
||||
inconsistentObject?.type === 'remote_relationship' &&
|
||||
inconsistentObject?.definition?.remote_schema
|
||||
) {
|
||||
if (!rsNameList.includes(inconsistentObject?.definition?.remote_schema)) {
|
||||
return [...rsNameList, inconsistentObject?.definition?.remote_schema];
|
||||
}
|
||||
}
|
||||
return rsNameList;
|
||||
}, []);
|
||||
|
Loading…
Reference in New Issue
Block a user