mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-12 18:47:45 +00:00
feat(react): configure Cypress + MirageJS + GraphQL mock for functional testing plus a couple of example tests (#2597)
This commit is contained in:
parent
79e76e8b89
commit
49de7aba66
@ -47,6 +47,20 @@ can run the following in this directory:
|
||||
which will start a forwarding server at `localhost:3000`. Note that to fetch real data, `datahub-frontend` server will also
|
||||
need to be deployed, still at `http://localhost:9002`, to service GraphQL API requests.
|
||||
|
||||
Optionally you could also start the app with the mock server without running the docker containers by executing `yarn start:mock`. See [here](src/graphql-mock/fixtures/searchResult/userSearchResult.ts#L6) for available login users.
|
||||
|
||||
### Functional testing
|
||||
|
||||
Automated functional testing is powered by Cypress and MirageJS. When running the web server with Cypress the port is set to 3010 so that the usual web server running on port 3000 used for development can be started without interruptions.
|
||||
|
||||
#### During development
|
||||
|
||||
`yarn test:e2e`
|
||||
|
||||
#### CI
|
||||
|
||||
`yarn test:e2e:ci`
|
||||
|
||||
### Theming
|
||||
|
||||
#### Selecting a theme
|
||||
|
||||
4
datahub-web-react/cypress.json
Normal file
4
datahub-web-react/cypress.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3010",
|
||||
"video": false
|
||||
}
|
||||
12
datahub-web-react/cypress/.eslintrc.js
Normal file
12
datahub-web-react/cypress/.eslintrc.js
Normal file
@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
extends: '../.eslintrc.js',
|
||||
parserOptions: {
|
||||
project: 'cypress/tsconfig.json',
|
||||
},
|
||||
globals: {
|
||||
Cypress: true,
|
||||
},
|
||||
rules: {
|
||||
'jest/expect-expect': 0,
|
||||
},
|
||||
};
|
||||
5
datahub-web-react/cypress/fixtures/example.json
Normal file
5
datahub-web-react/cypress/fixtures/example.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
11
datahub-web-react/cypress/helper/authHelper.ts
Normal file
11
datahub-web-react/cypress/helper/authHelper.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export const login = (username) => {
|
||||
cy.visit('/');
|
||||
cy.get('input#username').type(username);
|
||||
cy.get('input#password').type(username);
|
||||
cy.contains('Log in').click();
|
||||
};
|
||||
|
||||
export const logout = (username) => {
|
||||
cy.get(`a[href="/user/urn:li:corpuser:${username}"]`).children('.anticon.anticon-caret-down').trigger('mouseover');
|
||||
cy.get('li#user-profile-menu-logout').click();
|
||||
};
|
||||
40
datahub-web-react/cypress/integration/Login.spec.ts
Normal file
40
datahub-web-react/cypress/integration/Login.spec.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { createLoginUsers } from '../../src/graphql-mock/fixtures/user';
|
||||
import { makeServer } from '../../src/graphql-mock/server';
|
||||
import { login, logout } from '../helper/authHelper';
|
||||
|
||||
describe('Login', () => {
|
||||
let server;
|
||||
|
||||
beforeEach(() => {
|
||||
server = makeServer('test');
|
||||
createLoginUsers(server);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.shutdown();
|
||||
});
|
||||
|
||||
describe('given the login page is loaded', () => {
|
||||
describe('when logging in with incorrect credentials', () => {
|
||||
it('then the login should fail and the toast notification should be briefly displayed', () => {
|
||||
login('kafkaa');
|
||||
|
||||
cy.contains('Failed to log in!').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when logging in with correct credentials', () => {
|
||||
it('then the home page should be displayed', () => {
|
||||
login('kafka');
|
||||
|
||||
cy.contains('Welcome back,').should('be.visible');
|
||||
cy.contains('Datasets').should('be.visible');
|
||||
cy.contains('Dashboard').should('be.visible');
|
||||
cy.contains('Chart').should('be.visible');
|
||||
cy.contains('Pipelines').should('be.visible');
|
||||
|
||||
logout('kafka');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
36
datahub-web-react/cypress/integration/Search.spec.ts
Normal file
36
datahub-web-react/cypress/integration/Search.spec.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { createLoginUsers } from '../../src/graphql-mock/fixtures/user';
|
||||
import { makeServer } from '../../src/graphql-mock/server';
|
||||
import { login, logout } from '../helper/authHelper';
|
||||
|
||||
describe('Search', () => {
|
||||
let server;
|
||||
|
||||
beforeEach(() => {
|
||||
server = makeServer('test');
|
||||
createLoginUsers(server);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.shutdown();
|
||||
});
|
||||
|
||||
describe('given the home page is loaded', () => {
|
||||
describe('when the user enters a keyword in the search field and results found and the first item is selected from the search result dropdown', () => {
|
||||
it('then the search result page should be displayed with the Task tab be selected and the selected item be displayed', () => {
|
||||
login('kafka');
|
||||
|
||||
cy.get('input[placeholder="Search Datasets, People, & more..."]').type('load');
|
||||
|
||||
cy.get('div.rc-virtual-list-holder-inner')
|
||||
.children('div.ant-select-item.ant-select-item-option.ant-select-item-option-grouped')
|
||||
.contains('load_all_')
|
||||
.click();
|
||||
|
||||
cy.get('.ant-tabs-tab.ant-tabs-tab-active').contains('Task').should('be.visible');
|
||||
cy.contains('load_all_').should('be.visible');
|
||||
|
||||
logout('kafka');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
47
datahub-web-react/cypress/plugins/index.js
Normal file
47
datahub-web-react/cypress/plugins/index.js
Normal file
@ -0,0 +1,47 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
const findWebpack = require('find-webpack');
|
||||
const wp = require('@cypress/webpack-preprocessor');
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on, config) => {
|
||||
// find the Webpack config used by react-scripts
|
||||
const webpackOptions = findWebpack.getWebpackOptions();
|
||||
|
||||
if (!webpackOptions) {
|
||||
throw new Error('Could not find Webpack in this project 😢');
|
||||
}
|
||||
|
||||
const cleanOptions = {
|
||||
reactScripts: true,
|
||||
};
|
||||
|
||||
findWebpack.cleanForCypress(cleanOptions, webpackOptions);
|
||||
|
||||
const options = {
|
||||
webpackOptions,
|
||||
watchOptions: {},
|
||||
};
|
||||
|
||||
on('file:preprocessor', wp(options));
|
||||
|
||||
// add other tasks to be registered here
|
||||
|
||||
// IMPORTANT to return the config object
|
||||
// with the any changed environment variables
|
||||
return config;
|
||||
};
|
||||
20
datahub-web-react/cypress/support/index.js
Normal file
20
datahub-web-react/cypress/support/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
Cypress.on('window:before:load', (win) => {
|
||||
win.handleFromCypress = (request) => {
|
||||
return fetch(request.url, {
|
||||
method: request.method,
|
||||
headers: request.requestHeaders,
|
||||
body: request.requestBody,
|
||||
})
|
||||
.then((res) => {
|
||||
const content = res.headers.get('content-type').includes('application/json') ? res.json() : res.text();
|
||||
return new Promise((resolve) => {
|
||||
content.then((body) => resolve([res.status, res.headers, body]));
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Cypress request proxy error', { error });
|
||||
});
|
||||
};
|
||||
});
|
||||
10
datahub-web-react/cypress/tsconfig.json
Normal file
10
datahub-web-react/cypress/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"],
|
||||
"noEmit": false
|
||||
},
|
||||
"include": ["../node_modules/cypress", "**/*.ts", "support/index.js"]
|
||||
}
|
||||
@ -12,7 +12,9 @@
|
||||
"@ant-design/icons": "^4.3.0",
|
||||
"@apollo/client": "^3.3.19",
|
||||
"@craco/craco": "^6.1.1",
|
||||
"@cypress/webpack-preprocessor": "5.8.0",
|
||||
"@data-ui/xy-chart": "^0.0.84",
|
||||
"@miragejs/graphql": "^0.1.11",
|
||||
"@react-hook/window-size": "^3.0.7",
|
||||
"@testing-library/jest-dom": "^5.11.6",
|
||||
"@testing-library/react": "^11.2.2",
|
||||
@ -43,14 +45,20 @@
|
||||
"apollo-link-error": "^1.1.13",
|
||||
"apollo-link-http": "^1.5.17",
|
||||
"craco-antd": "^1.19.0",
|
||||
"cypress": "7.3.0",
|
||||
"d3-scale": "^3.3.0",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"diff": "^5.0.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"graphql": "^15.4.0",
|
||||
"faker": "5.5.3",
|
||||
"find-webpack": "2.2.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-tag": "2.10.3",
|
||||
"graphql.macro": "^1.4.2",
|
||||
"history": "^5.0.0",
|
||||
"js-cookie": "^2.2.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"miragejs": "^0.1.41",
|
||||
"query-string": "^6.13.8",
|
||||
"rc-table": "^7.13.1",
|
||||
"react": "^17.0.0",
|
||||
@ -60,6 +68,7 @@
|
||||
"react-router-dom": "^5.1.6",
|
||||
"react-scripts": "4.0.3",
|
||||
"sinon": "^11.1.1",
|
||||
"start-server-and-test": "1.12.2",
|
||||
"styled-components": "^5.2.1",
|
||||
"typescript": "^4.1.3",
|
||||
"uuid": "^8.3.2",
|
||||
@ -67,10 +76,17 @@
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "yarn run generate && BROWSER=none craco start",
|
||||
"start": "yarn run generate && BROWSER=none REACT_APP_MOCK=false craco start",
|
||||
"start:mock": "yarn run generate && BROWSER=none REACT_APP_MOCK=true craco start",
|
||||
"start:e2e": "REACT_APP_MOCK=cy BROWSER=none PORT=3010 craco start",
|
||||
"ec2-dev": "yarn run generate && CI=true;export CI;BROWSER=none craco start",
|
||||
"build": "yarn run generate && CI=false craco build && rm -rf dist/ && cp -r build/ dist/ && rm -r build/",
|
||||
"build": "yarn run generate && CI=false REACT_APP_MOCK=false craco build && rm -rf dist/ && cp -r build/ dist/ && rm -r build/",
|
||||
"test": "craco test",
|
||||
"cy:run:ci": "cypress run",
|
||||
"pretest:e2e:ci": "yarn generate",
|
||||
"test:e2e:ci": "start-server-and-test start:e2e 3010 cy:run:ci",
|
||||
"cy:open": "cypress open",
|
||||
"test:e2e": "start-server-and-test start:e2e 3010 cy:open",
|
||||
"eject": "react-scripts eject",
|
||||
"generate": "graphql-codegen --config codegen.yml",
|
||||
"lint": "eslint . --ext .ts,.tsx --quiet",
|
||||
@ -112,9 +128,9 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"http-proxy-middleware": "2.0.0",
|
||||
"prettier": "^2.3.0"
|
||||
},
|
||||
"proxy": "http://localhost:9002",
|
||||
"resolutions": {
|
||||
"@ant-design/colors": "5.0.0"
|
||||
}
|
||||
|
||||
@ -3,12 +3,10 @@ import Cookies from 'js-cookie';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ApolloClient, ApolloProvider, createHttpLink, InMemoryCache, ServerError } from '@apollo/client';
|
||||
import { onError } from '@apollo/client/link/error';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import './App.less';
|
||||
import { Routes } from './app/Routes';
|
||||
import { mocks } from './Mocks';
|
||||
import EntityRegistry from './app/entity/EntityRegistry';
|
||||
import { DashboardEntity } from './app/entity/dashboard/DashboardEntity';
|
||||
import { ChartEntity } from './app/entity/chart/ChartEntity';
|
||||
@ -26,11 +24,8 @@ import { isLoggedInVar } from './app/auth/checkAuthStatus';
|
||||
import { GlobalCfg } from './conf';
|
||||
import { GlossaryTermEntity } from './app/entity/glossaryTerm/GlossaryTermEntity';
|
||||
|
||||
// Enable to use the Apollo MockProvider instead of a real HTTP client
|
||||
const MOCK_MODE = false;
|
||||
|
||||
/*
|
||||
Construct Apollo Client
|
||||
Construct Apollo Client
|
||||
*/
|
||||
const httpLink = createHttpLink({ uri: '/api/v2/graphql' });
|
||||
|
||||
@ -112,23 +107,9 @@ const App: React.VFC = () => {
|
||||
<ThemeProvider theme={dynamicThemeConfig}>
|
||||
<Router>
|
||||
<EntityRegistryContext.Provider value={entityRegistry}>
|
||||
{/* Temporary: For local testing during development. */}
|
||||
{MOCK_MODE ? (
|
||||
<MockedProvider
|
||||
mocks={mocks}
|
||||
addTypename={false}
|
||||
defaultOptions={{
|
||||
watchQuery: { fetchPolicy: 'no-cache' },
|
||||
query: { fetchPolicy: 'no-cache' },
|
||||
}}
|
||||
>
|
||||
<Routes />
|
||||
</MockedProvider>
|
||||
) : (
|
||||
<ApolloProvider client={client}>
|
||||
<Routes />
|
||||
</ApolloProvider>
|
||||
)}
|
||||
<ApolloProvider client={client}>
|
||||
<Routes />
|
||||
</ApolloProvider>
|
||||
</EntityRegistryContext.Provider>
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
|
||||
@ -64,7 +64,9 @@ export const AllEntitiesSearchResults = ({ query }: Props) => {
|
||||
{Object.keys(allSearchResultsByType).map((type: any) => {
|
||||
const searchResults = allSearchResultsByType[type].data?.search?.searchResults;
|
||||
if (searchResults && searchResults.length > 0) {
|
||||
return <EntityGroupSearchResults type={type} query={query} searchResults={searchResults} />;
|
||||
return (
|
||||
<EntityGroupSearchResults key={type} type={type} query={query} searchResults={searchResults} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
|
||||
@ -65,7 +65,7 @@ export const ManageAccount = ({ urn: _urn, pictureLink: _pictureLink, name }: Pr
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
<MenuItem danger key="logout" onClick={handleLogout} tabIndex={0}>
|
||||
<MenuItem id="user-profile-menu-logout" danger key="logout" onClick={handleLogout} tabIndex={0}>
|
||||
Log out
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
12
datahub-web-react/src/graphql-mock/createServer.ts
Normal file
12
datahub-web-react/src/graphql-mock/createServer.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/* eslint-disable global-require */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
if (process.env.REACT_APP_MOCK === 'true' || process.env.REACT_APP_MOCK === 'cy') {
|
||||
if (process.env.REACT_APP_MOCK === 'cy') {
|
||||
require('./server').makeServerForCypress();
|
||||
} else {
|
||||
require('./server').makeServer();
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@ -0,0 +1,13 @@
|
||||
import { EntityType } from '../../../types.generated';
|
||||
import { BrowsePathResolver } from '../browsePathHelper';
|
||||
import { chartBrowsePaths, filterChartByPath } from '../searchResult/chartSearchResult';
|
||||
|
||||
const browsePathResolver = new BrowsePathResolver({
|
||||
entityType: EntityType.Chart,
|
||||
paths: chartBrowsePaths,
|
||||
filterEntityHandler: filterChartByPath,
|
||||
});
|
||||
|
||||
export default {
|
||||
...browsePathResolver.getBrowse(),
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { EntityType } from '../../../types.generated';
|
||||
import { BrowsePathResolver } from '../browsePathHelper';
|
||||
import { dashboardBrowsePaths, filterDashboardByPath } from '../searchResult/dashboardSearchResult';
|
||||
|
||||
const browsePathResolver = new BrowsePathResolver({
|
||||
entityType: EntityType.Dashboard,
|
||||
paths: dashboardBrowsePaths,
|
||||
filterEntityHandler: filterDashboardByPath,
|
||||
});
|
||||
|
||||
export default {
|
||||
...browsePathResolver.getBrowse(),
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { EntityType } from '../../../types.generated';
|
||||
import { BrowsePathResolver } from '../browsePathHelper';
|
||||
import { dataFlowBrowsePaths, filterDataFlowByPath } from '../searchResult/dataFlowSearchResult';
|
||||
|
||||
const browsePathResolver = new BrowsePathResolver({
|
||||
entityType: EntityType.DataFlow,
|
||||
paths: dataFlowBrowsePaths,
|
||||
filterEntityHandler: filterDataFlowByPath,
|
||||
});
|
||||
|
||||
export default {
|
||||
...browsePathResolver.getBrowse(),
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { EntityType } from '../../../types.generated';
|
||||
import { BrowsePathResolver } from '../browsePathHelper';
|
||||
import { datasetBrowsePaths, filterDatasetByPath } from '../searchResult/datasetSearchResult';
|
||||
|
||||
const browsePathResolver = new BrowsePathResolver({
|
||||
entityType: EntityType.Dataset,
|
||||
paths: datasetBrowsePaths,
|
||||
filterEntityHandler: filterDatasetByPath,
|
||||
});
|
||||
|
||||
export default {
|
||||
...browsePathResolver.getBrowse(),
|
||||
};
|
||||
159
datahub-web-react/src/graphql-mock/fixtures/browsePathHelper.ts
Normal file
159
datahub-web-react/src/graphql-mock/fixtures/browsePathHelper.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { BrowseInput, BrowseResultGroup, BrowseResults, Entity, EntityType, SearchResult } from '../../types.generated';
|
||||
import { toLowerCaseEntityType, toTitleCase } from '../helper';
|
||||
import { EntityBrowseFn, EntityBrowsePath, GetBrowseResults, StringNumber } from '../types';
|
||||
|
||||
type ToFlatPathsArg = {
|
||||
flatPaths: StringNumber[][];
|
||||
paths: EntityBrowsePath[];
|
||||
parentPaths: string[];
|
||||
};
|
||||
|
||||
export const toFlatPaths = ({ flatPaths, paths, parentPaths }: ToFlatPathsArg) => {
|
||||
paths.forEach(({ name, paths: childPaths, count = 0 }) => {
|
||||
if (childPaths.length) {
|
||||
parentPaths.push(name);
|
||||
toFlatPaths({ flatPaths, parentPaths, paths: childPaths });
|
||||
} else {
|
||||
flatPaths.push([...parentPaths, name, count]);
|
||||
}
|
||||
});
|
||||
parentPaths.pop();
|
||||
};
|
||||
|
||||
type FilterEntityByPathArg = {
|
||||
term: string;
|
||||
searchResults: SearchResult[];
|
||||
};
|
||||
|
||||
export const filterEntityByPath = ({ term, searchResults }: FilterEntityByPathArg): Entity[] => {
|
||||
return searchResults
|
||||
.filter((r) => {
|
||||
const regex = new RegExp(term);
|
||||
return regex.test(r.entity.urn);
|
||||
})
|
||||
.map((r) => r.entity);
|
||||
};
|
||||
|
||||
export class BrowsePathResolver {
|
||||
private readonly browse: Record<string, EntityBrowseFn>;
|
||||
|
||||
private readonly paths: EntityBrowsePath[];
|
||||
|
||||
private readonly filterEntityHandler: (path: string[]) => Entity[];
|
||||
|
||||
private readonly baseBrowseResult: GetBrowseResults = {
|
||||
data: {
|
||||
browse: {
|
||||
entities: [],
|
||||
start: 0,
|
||||
count: 0,
|
||||
total: 0,
|
||||
metadata: {
|
||||
path: [],
|
||||
groups: [],
|
||||
totalNumEntities: 0,
|
||||
__typename: 'BrowseResultMetadata',
|
||||
},
|
||||
__typename: 'BrowseResults',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
constructor({
|
||||
entityType,
|
||||
paths,
|
||||
filterEntityHandler,
|
||||
}: {
|
||||
entityType: EntityType;
|
||||
paths: EntityBrowsePath[];
|
||||
filterEntityHandler(path: string[]): Entity[];
|
||||
}) {
|
||||
this.browse = {};
|
||||
this.paths = paths;
|
||||
this.filterEntityHandler = filterEntityHandler;
|
||||
const browsePathKey = `${toLowerCaseEntityType(entityType)}Browse`;
|
||||
const groups = this.paths.map<BrowseResultGroup>(({ name, paths: rootPaths, count = 0 }) => ({
|
||||
name,
|
||||
count: rootPaths.length ? rootPaths.reduce(this.sumTotalEntityByPaths, 0) : count,
|
||||
__typename: 'BrowseResultGroup',
|
||||
}));
|
||||
this.initBrowsePathResolver({ browsePathKey, groups });
|
||||
this.initBrowsePathResolverForPaths({ prefixPathKey: browsePathKey, paths });
|
||||
}
|
||||
|
||||
public getBrowse() {
|
||||
return this.browse;
|
||||
}
|
||||
|
||||
private initBrowsePathResolver({ browsePathKey, groups }: { browsePathKey: string; groups: BrowseResultGroup[] }) {
|
||||
if (!this.browse.hasOwnProperty(browsePathKey)) {
|
||||
const dataBrowse: BrowseResults = JSON.parse(JSON.stringify(this.baseBrowseResult.data.browse));
|
||||
|
||||
Object.assign(this.browse, {
|
||||
[browsePathKey]: ({ start, count, path }: BrowseInput): GetBrowseResults => {
|
||||
const startValue = start as number;
|
||||
const countValue = count as number;
|
||||
const paths = path as string[];
|
||||
const entities = groups.length ? [] : this.filterEntityHandler(paths);
|
||||
const chunkEntities = entities.slice(startValue, startValue + countValue);
|
||||
|
||||
return {
|
||||
data: {
|
||||
browse: {
|
||||
...dataBrowse,
|
||||
entities: chunkEntities,
|
||||
start: startValue,
|
||||
count: chunkEntities.length,
|
||||
total: entities.length,
|
||||
metadata: {
|
||||
...dataBrowse.metadata,
|
||||
path: paths,
|
||||
groups,
|
||||
totalNumEntities: groups.reduce(this.sumTotalEntityByGroups, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private initBrowsePathResolverForPaths({
|
||||
prefixPathKey,
|
||||
paths,
|
||||
}: {
|
||||
prefixPathKey: string;
|
||||
paths: EntityBrowsePath[];
|
||||
}) {
|
||||
paths.forEach(({ name, paths: childPaths }) => {
|
||||
const browsePathKey = `${prefixPathKey}${toTitleCase(name)}`;
|
||||
|
||||
if (childPaths.length) {
|
||||
const groups = childPaths.map<BrowseResultGroup>(
|
||||
({ name: childName, paths: child2Paths, count = 0 }) => ({
|
||||
name: childName,
|
||||
count: child2Paths.length ? child2Paths.reduce(this.sumTotalEntityByPaths, 0) : count,
|
||||
__typename: 'BrowseResultGroup',
|
||||
}),
|
||||
);
|
||||
|
||||
this.initBrowsePathResolver({ browsePathKey, groups });
|
||||
this.initBrowsePathResolverForPaths({ prefixPathKey: browsePathKey, paths: childPaths });
|
||||
} else {
|
||||
this.initBrowsePathResolver({ browsePathKey, groups: [] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sumTotalEntityByGroups = (out: number, { count = 0 }: BrowseResultGroup): number => {
|
||||
return out + count;
|
||||
};
|
||||
|
||||
private sumTotalEntityByPaths = (out: number, { paths, count = 0 }: EntityBrowsePath): number => {
|
||||
if (paths.length) {
|
||||
return paths.reduce(this.sumTotalEntityByPaths, out);
|
||||
}
|
||||
return out + count;
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import * as faker from 'faker';
|
||||
import { Chart, ChartType, EntityType, OwnershipType } from '../../../types.generated';
|
||||
import { findUserByUsername } from '../searchResult/userSearchResult';
|
||||
|
||||
export const chartEntity = (tool): Chart => {
|
||||
const name = `${faker.company.bsNoun()}_${faker.company.bsNoun()}_${faker.company.bsNoun()}`;
|
||||
const description = `${faker.commerce.productDescription()}`;
|
||||
const datahubUser = findUserByUsername('datahub');
|
||||
|
||||
return {
|
||||
urn: `urn:li:chart:(${tool},${name})`,
|
||||
type: EntityType.Chart,
|
||||
tool,
|
||||
chartId: '2',
|
||||
info: {
|
||||
name,
|
||||
description,
|
||||
externalUrl:
|
||||
'https://superset.demo.datahubproject.io/superset/explore/?form_data=%7B%22slice_id%22%3A%202%7D',
|
||||
type: ChartType.Pie,
|
||||
access: null,
|
||||
lastModified: { time: 1619137330, __typename: 'AuditStamp' },
|
||||
created: { time: 1619137330, __typename: 'AuditStamp' },
|
||||
__typename: 'ChartInfo',
|
||||
},
|
||||
editableProperties: null,
|
||||
ownership: {
|
||||
owners: [
|
||||
{
|
||||
owner: datahubUser,
|
||||
type: OwnershipType.Stakeholder,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
],
|
||||
lastModified: { time: 1619717962718, __typename: 'AuditStamp' },
|
||||
__typename: 'Ownership',
|
||||
},
|
||||
globalTags: null,
|
||||
__typename: 'Chart',
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,87 @@
|
||||
import * as faker from 'faker';
|
||||
import { generateTag } from '../tag';
|
||||
import { Dashboard, EntityType, Ownership, OwnershipType } from '../../../types.generated';
|
||||
import { findUserByUsername } from '../searchResult/userSearchResult';
|
||||
|
||||
export const dashboardEntity = (tool): Dashboard => {
|
||||
const name = `${faker.company.bsNoun()}`;
|
||||
const description = `${faker.commerce.productDescription()}`;
|
||||
const datahubUser = findUserByUsername('datahub');
|
||||
const kafkaUser = findUserByUsername('kafka');
|
||||
const lookerUser = findUserByUsername('looker');
|
||||
const datahubOwnership: Ownership = {
|
||||
owners: [
|
||||
{
|
||||
owner: datahubUser,
|
||||
type: OwnershipType.Stakeholder,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
],
|
||||
lastModified: { time: 1619993818664, __typename: 'AuditStamp' },
|
||||
__typename: 'Ownership',
|
||||
};
|
||||
const kafkaOwnership: Ownership = {
|
||||
owners: [
|
||||
{
|
||||
owner: kafkaUser,
|
||||
type: OwnershipType.Stakeholder,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
],
|
||||
lastModified: { time: 1619993818664, __typename: 'AuditStamp' },
|
||||
__typename: 'Ownership',
|
||||
};
|
||||
|
||||
return {
|
||||
urn: `urn:li:dashboard:(${tool},${name})`,
|
||||
type: EntityType.Dashboard,
|
||||
tool,
|
||||
dashboardId: '3',
|
||||
editableProperties: null,
|
||||
info: {
|
||||
name,
|
||||
description,
|
||||
externalUrl: null,
|
||||
access: null,
|
||||
charts: [],
|
||||
lastModified: { time: 1619160920, __typename: 'AuditStamp' },
|
||||
created: { time: 1619160920, __typename: 'AuditStamp' },
|
||||
__typename: 'DashboardInfo',
|
||||
},
|
||||
ownership: {
|
||||
owners: [
|
||||
{
|
||||
owner: datahubUser,
|
||||
type: OwnershipType.Stakeholder,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
{
|
||||
owner: kafkaUser,
|
||||
type: OwnershipType.Developer,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
{
|
||||
owner: lookerUser,
|
||||
type: OwnershipType.Developer,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
],
|
||||
lastModified: { time: 1619993818664, __typename: 'AuditStamp' },
|
||||
__typename: 'Ownership',
|
||||
},
|
||||
globalTags: {
|
||||
tags: [
|
||||
{
|
||||
tag: generateTag(datahubOwnership),
|
||||
__typename: 'TagAssociation',
|
||||
},
|
||||
{
|
||||
tag: generateTag(kafkaOwnership),
|
||||
__typename: 'TagAssociation',
|
||||
},
|
||||
],
|
||||
__typename: 'GlobalTags',
|
||||
},
|
||||
__typename: 'Dashboard',
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import * as faker from 'faker';
|
||||
import { DataFlow, EntityType, OwnershipType } from '../../../types.generated';
|
||||
import { findUserByUsername } from '../searchResult/userSearchResult';
|
||||
|
||||
export type DataFlowEntityArg = {
|
||||
orchestrator: string;
|
||||
cluster: string;
|
||||
};
|
||||
|
||||
export const dataFlowEntity = ({ orchestrator, cluster }: DataFlowEntityArg): DataFlow => {
|
||||
const flowId = `${faker.company.bsNoun()}_${faker.company.bsNoun()}`;
|
||||
const description = `${faker.commerce.productDescription()}`;
|
||||
const datahubUser = findUserByUsername('datahub');
|
||||
|
||||
return {
|
||||
urn: `urn:li:dataFlow:(${orchestrator},${flowId},${cluster})`,
|
||||
type: EntityType.DataFlow,
|
||||
orchestrator,
|
||||
flowId,
|
||||
cluster,
|
||||
info: {
|
||||
name: flowId,
|
||||
description,
|
||||
project: null,
|
||||
__typename: 'DataFlowInfo',
|
||||
},
|
||||
editableProperties: null,
|
||||
ownership: {
|
||||
owners: [
|
||||
{
|
||||
owner: datahubUser,
|
||||
type: OwnershipType.Stakeholder,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
],
|
||||
lastModified: { time: 1620224528712, __typename: 'AuditStamp' },
|
||||
__typename: 'Ownership',
|
||||
},
|
||||
globalTags: { tags: [], __typename: 'GlobalTags' },
|
||||
dataJobs: null,
|
||||
__typename: 'DataFlow',
|
||||
};
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,86 @@
|
||||
import * as faker from 'faker';
|
||||
import { DataPlatform, Dataset, EntityType, FabricType, OwnershipType, PlatformType } from '../../../types.generated';
|
||||
import kafkaLogo from '../../../images/kafkalogo.png';
|
||||
import s3Logo from '../../../images/s3.png';
|
||||
import snowflakeLogo from '../../../images/snowflakelogo.png';
|
||||
import bigqueryLogo from '../../../images/bigquerylogo.png';
|
||||
import { findUserByUsername } from '../searchResult/userSearchResult';
|
||||
|
||||
const platformLogo = {
|
||||
kafka: kafkaLogo,
|
||||
s3: s3Logo,
|
||||
snowflake: snowflakeLogo,
|
||||
bigquery: bigqueryLogo,
|
||||
};
|
||||
|
||||
const generatePlatform = ({ platform, urn }): DataPlatform => {
|
||||
return {
|
||||
urn,
|
||||
type: EntityType.Dataset,
|
||||
name: platform,
|
||||
info: {
|
||||
type: PlatformType.Others,
|
||||
datasetNameDelimiter: '',
|
||||
logoUrl: platformLogo[platform],
|
||||
__typename: 'DataPlatformInfo',
|
||||
},
|
||||
__typename: 'DataPlatform',
|
||||
};
|
||||
};
|
||||
|
||||
export type DatasetEntityArg = {
|
||||
platform: string;
|
||||
origin: FabricType;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export const datasetEntity = ({
|
||||
platform,
|
||||
origin,
|
||||
path,
|
||||
}: DatasetEntityArg): Dataset & { previousSchemaMetadata: any } => {
|
||||
const name = `${path}.${faker.company.bsNoun()}_${faker.company.bsNoun()}`;
|
||||
const description = `${faker.commerce.productDescription()}`;
|
||||
const datahubUser = findUserByUsername('datahub');
|
||||
const platformURN = `urn:li:dataPlatform:${platform}`;
|
||||
|
||||
return {
|
||||
urn: `urn:li:dataset:(${platformURN},${name},${origin.toUpperCase()})`,
|
||||
type: EntityType.Dataset,
|
||||
name,
|
||||
origin,
|
||||
description,
|
||||
uri: null,
|
||||
platform: generatePlatform({ platform, urn: platformURN }),
|
||||
platformNativeType: null,
|
||||
tags: [],
|
||||
properties: null,
|
||||
editableProperties: null,
|
||||
editableSchemaMetadata: null,
|
||||
deprecation: null,
|
||||
ownership: {
|
||||
owners: [
|
||||
{
|
||||
owner: datahubUser,
|
||||
type: OwnershipType.Dataowner,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
],
|
||||
lastModified: {
|
||||
time: 1616107219521,
|
||||
__typename: 'AuditStamp',
|
||||
},
|
||||
__typename: 'Ownership',
|
||||
},
|
||||
globalTags: {
|
||||
tags: [],
|
||||
__typename: 'GlobalTags',
|
||||
},
|
||||
institutionalMemory: null,
|
||||
usageStats: null,
|
||||
glossaryTerms: null,
|
||||
schemaMetadata: null,
|
||||
previousSchemaMetadata: null,
|
||||
__typename: 'Dataset',
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
import * as faker from 'faker';
|
||||
import { CorpUser, EntityType } from '../../../types.generated';
|
||||
|
||||
export type UserEntityArg = {
|
||||
username: string;
|
||||
};
|
||||
|
||||
export const userEntity = (option?: UserEntityArg): CorpUser => {
|
||||
const username = option?.username || `${faker.internet.userName()}`;
|
||||
const title = `${faker.name.title()}`;
|
||||
const firstName = `${faker.name.firstName()}`;
|
||||
const lastName = `${faker.name.lastName()}`;
|
||||
const email = `${faker.internet.email()}`;
|
||||
|
||||
return {
|
||||
urn: `urn:li:corpuser:${username}`,
|
||||
type: EntityType.CorpUser,
|
||||
username,
|
||||
info: {
|
||||
active: true,
|
||||
displayName: `${firstName} ${lastName}`,
|
||||
title,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName: `${firstName} ${lastName}`,
|
||||
__typename: 'CorpUserInfo',
|
||||
},
|
||||
editableInfo: {
|
||||
aboutMe: `about ${username}`,
|
||||
teams: [faker.company.companyName(), faker.company.companyName()],
|
||||
skills: [faker.company.catchPhrase(), faker.company.catchPhrase()],
|
||||
pictureLink: null,
|
||||
__typename: 'CorpUserEditableInfo',
|
||||
},
|
||||
__typename: 'CorpUser',
|
||||
};
|
||||
};
|
||||
5
datahub-web-react/src/graphql-mock/fixtures/index.ts
Normal file
5
datahub-web-react/src/graphql-mock/fixtures/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './searchResult';
|
||||
export { default as datasetBrowseResult } from './browseDataset';
|
||||
export { default as dashboardBrowseResult } from './browseDashboard';
|
||||
export { default as chartBrowseResult } from './browseChart';
|
||||
export { default as dataflowBrowseResult } from './browseDataFlow';
|
||||
@ -0,0 +1,68 @@
|
||||
import { Chart, SearchResult, SearchResults } from '../../../types.generated';
|
||||
import { EntityBrowsePath } from '../../types';
|
||||
import { filterEntityByPath } from '../browsePathHelper';
|
||||
import { chartEntity } from '../entity/chartEntity';
|
||||
import { generateData } from './dataGenerator';
|
||||
|
||||
const searchResult = (tool: string) => (): SearchResult => {
|
||||
return {
|
||||
entity: chartEntity(tool),
|
||||
matchedFields: [],
|
||||
__typename: 'SearchResult',
|
||||
};
|
||||
};
|
||||
|
||||
export const chartBrowsePaths: EntityBrowsePath[] = [{ name: 'superset', paths: [], count: 6 }];
|
||||
|
||||
const generateSearchResults = (): SearchResult[] => {
|
||||
return chartBrowsePaths.flatMap(({ name, count = 0 }) => {
|
||||
return generateData<SearchResult>({ generator: searchResult(name), count });
|
||||
});
|
||||
};
|
||||
|
||||
const searchResults = generateSearchResults();
|
||||
|
||||
export const chartSearchResult: SearchResults = {
|
||||
start: 0,
|
||||
count: 0,
|
||||
total: 0,
|
||||
searchResults,
|
||||
facets: [
|
||||
{ field: 'access', aggregations: [], __typename: 'FacetMetadata' },
|
||||
{
|
||||
field: 'type',
|
||||
aggregations: [
|
||||
{ value: 'TABLE', count: 1, __typename: 'AggregationMetadata' },
|
||||
{ value: 'BAR', count: 3, __typename: 'AggregationMetadata' },
|
||||
{ value: 'PIE', count: 1, __typename: 'AggregationMetadata' },
|
||||
{ value: 'LINE', count: 1, __typename: 'AggregationMetadata' },
|
||||
],
|
||||
__typename: 'FacetMetadata',
|
||||
},
|
||||
{
|
||||
field: 'tool',
|
||||
aggregations: [{ value: 'superset', count: 6, __typename: 'AggregationMetadata' }],
|
||||
__typename: 'FacetMetadata',
|
||||
},
|
||||
{ field: 'queryType', aggregations: [], __typename: 'FacetMetadata' },
|
||||
],
|
||||
__typename: 'SearchResults',
|
||||
};
|
||||
|
||||
export const filterChartByTool = (tool: string): Chart[] => {
|
||||
return searchResults
|
||||
.filter((r) => {
|
||||
return (r.entity as Chart).tool === tool;
|
||||
})
|
||||
.map((r) => r.entity as Chart);
|
||||
};
|
||||
|
||||
export const findChartByURN = (urn: string): Chart => {
|
||||
return searchResults.find((r) => {
|
||||
return (r.entity as Chart).urn === urn;
|
||||
})?.entity as Chart;
|
||||
};
|
||||
|
||||
export const filterChartByPath = (path: string[]): Chart[] => {
|
||||
return filterEntityByPath({ term: path.slice(-2).join('.'), searchResults }) as Chart[];
|
||||
};
|
||||
@ -0,0 +1,57 @@
|
||||
import { Dashboard, SearchResult, SearchResults } from '../../../types.generated';
|
||||
import { EntityBrowsePath } from '../../types';
|
||||
import { filterEntityByPath } from '../browsePathHelper';
|
||||
import { dashboardEntity } from '../entity/dashboardEntity';
|
||||
import { generateData } from './dataGenerator';
|
||||
|
||||
const searchResult = (tool: string) => (): SearchResult => {
|
||||
return {
|
||||
entity: dashboardEntity(tool),
|
||||
matchedFields: [],
|
||||
__typename: 'SearchResult',
|
||||
};
|
||||
};
|
||||
|
||||
export const dashboardBrowsePaths: EntityBrowsePath[] = [{ name: 'superset', paths: [], count: 3 }];
|
||||
|
||||
const generateSearchResults = (): SearchResult[] => {
|
||||
return dashboardBrowsePaths.flatMap(({ name, count = 0 }) => {
|
||||
return generateData<SearchResult>({ generator: searchResult(name), count });
|
||||
});
|
||||
};
|
||||
|
||||
const searchResults = generateSearchResults();
|
||||
|
||||
export const dashboardSearchResult: SearchResults = {
|
||||
start: 0,
|
||||
count: 0,
|
||||
total: 0,
|
||||
searchResults,
|
||||
facets: [
|
||||
{
|
||||
field: 'tool',
|
||||
aggregations: [{ value: 'superset', count: 1, __typename: 'AggregationMetadata' }],
|
||||
__typename: 'FacetMetadata',
|
||||
},
|
||||
{ field: 'access', aggregations: [], __typename: 'FacetMetadata' },
|
||||
],
|
||||
__typename: 'SearchResults',
|
||||
};
|
||||
|
||||
export const filterDashboardByTool = (tool: string): Dashboard[] => {
|
||||
return searchResults
|
||||
.filter((r) => {
|
||||
return (r.entity as Dashboard).tool === tool;
|
||||
})
|
||||
.map((r) => r.entity as Dashboard);
|
||||
};
|
||||
|
||||
export const findDashboardByURN = (urn: string): Dashboard => {
|
||||
return searchResults.find((r) => {
|
||||
return (r.entity as Dashboard).urn === urn;
|
||||
})?.entity as Dashboard;
|
||||
};
|
||||
|
||||
export const filterDashboardByPath = (path: string[]): Dashboard[] => {
|
||||
return filterEntityByPath({ term: path.slice(-2).join('.'), searchResults }) as Dashboard[];
|
||||
};
|
||||
@ -0,0 +1,72 @@
|
||||
import { DataFlow, SearchResult, SearchResults } from '../../../types.generated';
|
||||
import { EntityBrowsePath } from '../../types';
|
||||
import { filterEntityByPath } from '../browsePathHelper';
|
||||
import { dataFlowEntity, DataFlowEntityArg } from '../entity/dataFlowEntity';
|
||||
import { generateData } from './dataGenerator';
|
||||
|
||||
type SearchResultArg = DataFlowEntityArg;
|
||||
|
||||
const searchResult =
|
||||
({ orchestrator, cluster }: SearchResultArg) =>
|
||||
(): SearchResult => {
|
||||
return {
|
||||
entity: dataFlowEntity({ orchestrator, cluster }),
|
||||
matchedFields: [],
|
||||
__typename: 'SearchResult',
|
||||
};
|
||||
};
|
||||
|
||||
export const dataFlowBrowsePaths: EntityBrowsePath[] = [
|
||||
{
|
||||
name: 'airflow',
|
||||
paths: [
|
||||
{
|
||||
name: 'prod',
|
||||
paths: [],
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
name: 'dev',
|
||||
paths: [],
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const generateSearchResults = (): SearchResult[] => {
|
||||
return dataFlowBrowsePaths.flatMap(({ name: orchestrator, paths }) => {
|
||||
return paths.flatMap(({ name: cluster, count = 0 }) => {
|
||||
return generateData<SearchResult>({ generator: searchResult({ orchestrator, cluster }), count });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const searchResults = generateSearchResults();
|
||||
|
||||
export const dataFlowSearchResult: SearchResults = {
|
||||
start: 0,
|
||||
count: 0,
|
||||
total: 0,
|
||||
searchResults,
|
||||
facets: [],
|
||||
__typename: 'SearchResults',
|
||||
};
|
||||
|
||||
export const filterDataFlowByOrchestrator = (orchestrator: string): DataFlow[] => {
|
||||
return searchResults
|
||||
.filter((r) => {
|
||||
return (r.entity as DataFlow).orchestrator === orchestrator;
|
||||
})
|
||||
.map((r) => r.entity as DataFlow);
|
||||
};
|
||||
|
||||
export const findDataFlowByURN = (urn: string): DataFlow => {
|
||||
return searchResults.find((r) => {
|
||||
return (r.entity as DataFlow).urn === urn;
|
||||
})?.entity as DataFlow;
|
||||
};
|
||||
|
||||
export const filterDataFlowByPath = (path: string[]): DataFlow[] => {
|
||||
return filterEntityByPath({ term: path.slice(-2).join(',[\\s\\S]+,'), searchResults }) as DataFlow[];
|
||||
};
|
||||
@ -0,0 +1,16 @@
|
||||
import { AnyRecord } from '../../types';
|
||||
|
||||
type GenerateDataArg<T = AnyRecord> = {
|
||||
generator(): T;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export const times = (count: number) => {
|
||||
return Array.from(new Array(count));
|
||||
};
|
||||
|
||||
export const generateData = <T = AnyRecord>({ generator, count }: GenerateDataArg<T>): T[] => {
|
||||
return times(count).map(() => {
|
||||
return generator();
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import { DataJob, SearchResult, SearchResults } from '../../../types.generated';
|
||||
import { dataJobEntity } from '../entity/dataJobEntity';
|
||||
import { generateData } from './dataGenerator';
|
||||
|
||||
const searchResult = (): SearchResult => {
|
||||
return {
|
||||
entity: dataJobEntity(),
|
||||
matchedFields: [],
|
||||
__typename: 'SearchResult',
|
||||
};
|
||||
};
|
||||
|
||||
const generateSearchResults = (): SearchResult[] => {
|
||||
return generateData<SearchResult>({ generator: searchResult, count: 2 });
|
||||
};
|
||||
|
||||
const searchResults = generateSearchResults();
|
||||
|
||||
export const dataJobSearchResult: SearchResults = {
|
||||
start: 0,
|
||||
count: 0,
|
||||
total: 0,
|
||||
searchResults,
|
||||
facets: [],
|
||||
__typename: 'SearchResults',
|
||||
};
|
||||
|
||||
export const findDataJobByURN = (urn: string): DataJob => {
|
||||
return searchResults.find((r) => {
|
||||
return (r.entity as DataJob).urn === urn;
|
||||
})?.entity as DataJob;
|
||||
};
|
||||
@ -0,0 +1,280 @@
|
||||
import { generateData } from './dataGenerator';
|
||||
import { datasetEntity, DatasetEntityArg } from '../entity/datasetEntity';
|
||||
import { Dataset, FabricType, SearchResult, SearchResults } from '../../../types.generated';
|
||||
import { EntityBrowsePath, StringNumber } from '../../types';
|
||||
import { filterEntityByPath, toFlatPaths } from '../browsePathHelper';
|
||||
|
||||
type SearchResultArg = DatasetEntityArg;
|
||||
|
||||
const searchResult =
|
||||
({ platform, origin, path }: SearchResultArg) =>
|
||||
(): SearchResult => {
|
||||
return {
|
||||
entity: datasetEntity({ platform, origin, path }),
|
||||
matchedFields: [],
|
||||
__typename: 'SearchResult',
|
||||
};
|
||||
};
|
||||
|
||||
export const datasetBrowsePaths: EntityBrowsePath[] = [
|
||||
{
|
||||
name: FabricType.Prod.toLowerCase(),
|
||||
paths: [
|
||||
{
|
||||
name: 'kafka',
|
||||
paths: [
|
||||
{
|
||||
name: 'australia',
|
||||
paths: [
|
||||
{
|
||||
name: 'queensland',
|
||||
paths: [
|
||||
{
|
||||
name: 'brisbane',
|
||||
paths: [
|
||||
{
|
||||
name: 'queensland-brisbane-sunnybank-vaccine-test',
|
||||
paths: [
|
||||
{
|
||||
name: 'topic-queensland-brisbane-sunnybank-vaccine-test-jan',
|
||||
paths: [],
|
||||
count: 2,
|
||||
},
|
||||
{
|
||||
name: 'topic-queensland-brisbane-sunnybank-vaccine-test-feb',
|
||||
paths: [],
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'topic-queensland-brisbane-carindale-vaccine-test-jan',
|
||||
paths: [],
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'topic-queensland-sunshine-coast-vaccine-test-feb',
|
||||
paths: [],
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'victoria',
|
||||
paths: [
|
||||
{
|
||||
name: 'topic-victoria-vaccine-test-mar',
|
||||
paths: [],
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'topic-papua-new-guinea-digital-transformation',
|
||||
paths: [],
|
||||
count: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 's3',
|
||||
paths: [
|
||||
{
|
||||
name: 'datahub-demo-backup',
|
||||
paths: [
|
||||
{
|
||||
name: 'demo',
|
||||
paths: [],
|
||||
count: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'datahubproject-demo-pipelines',
|
||||
paths: [
|
||||
{
|
||||
name: 'entity_aspect_splits',
|
||||
paths: [],
|
||||
count: 21,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'snowflake',
|
||||
paths: [
|
||||
{
|
||||
name: 'mydb',
|
||||
paths: [
|
||||
{
|
||||
name: 'schema',
|
||||
paths: [],
|
||||
count: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'demo_pipeline',
|
||||
paths: [
|
||||
{
|
||||
name: 'public',
|
||||
paths: [],
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'bigquery',
|
||||
paths: [
|
||||
{
|
||||
name: 'bigquery-pubic-data',
|
||||
paths: [
|
||||
{
|
||||
name: 'covid19-ecdc',
|
||||
paths: [],
|
||||
count: 1,
|
||||
},
|
||||
{
|
||||
name: 'covid19-open-data',
|
||||
paths: [],
|
||||
count: 24,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: FabricType.Dev.toLowerCase(),
|
||||
paths: [
|
||||
{
|
||||
name: 'kafka',
|
||||
paths: [
|
||||
{
|
||||
name: 'topic-mysql-customer-schema',
|
||||
paths: [],
|
||||
count: 1,
|
||||
},
|
||||
{
|
||||
name: 'rnd',
|
||||
paths: [
|
||||
{
|
||||
name: 'topic-mysql-marketing-ml-schema',
|
||||
paths: [],
|
||||
count: 3,
|
||||
},
|
||||
{
|
||||
name: 'topic-mysql-sales-ml-schema',
|
||||
paths: [],
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const generateSearchResults = (): SearchResult[] => {
|
||||
return datasetBrowsePaths.flatMap(({ name: origin, paths }) => {
|
||||
return paths.flatMap(({ name: platform, paths: childPaths }) => {
|
||||
const flatPaths: StringNumber[][] = [];
|
||||
const parentPaths: string[] = [];
|
||||
|
||||
toFlatPaths({ flatPaths, paths: childPaths, parentPaths });
|
||||
|
||||
return flatPaths.flatMap((fp) => {
|
||||
const count = fp.pop() as number;
|
||||
const path = fp.join('.');
|
||||
return generateData<SearchResult>({
|
||||
generator: searchResult({ platform, origin: origin.toUpperCase() as FabricType, path }),
|
||||
count,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const searchResults = generateSearchResults();
|
||||
|
||||
export const datasetSearchResult: SearchResults = {
|
||||
start: 0,
|
||||
count: 0,
|
||||
total: 0,
|
||||
searchResults,
|
||||
facets: [
|
||||
{
|
||||
field: 'platform',
|
||||
aggregations: [
|
||||
{
|
||||
value: 's3',
|
||||
count: 22,
|
||||
__typename: 'AggregationMetadata',
|
||||
},
|
||||
{
|
||||
value: 'snowflake',
|
||||
count: 69,
|
||||
__typename: 'AggregationMetadata',
|
||||
},
|
||||
{
|
||||
value: 'bigquery',
|
||||
count: 104,
|
||||
__typename: 'AggregationMetadata',
|
||||
},
|
||||
{
|
||||
value: 'kafka',
|
||||
count: 7,
|
||||
__typename: 'AggregationMetadata',
|
||||
},
|
||||
],
|
||||
__typename: 'FacetMetadata',
|
||||
},
|
||||
{
|
||||
field: 'origin',
|
||||
aggregations: [
|
||||
{
|
||||
value: 'prod',
|
||||
count: 202,
|
||||
__typename: 'AggregationMetadata',
|
||||
},
|
||||
],
|
||||
__typename: 'FacetMetadata',
|
||||
},
|
||||
],
|
||||
__typename: 'SearchResults',
|
||||
};
|
||||
|
||||
export const filterDatasetByPlatform = (platform: string): Dataset[] => {
|
||||
return searchResults
|
||||
.filter((r) => {
|
||||
return (r.entity as Dataset).platform.name === platform;
|
||||
})
|
||||
.map((r) => r.entity as Dataset);
|
||||
};
|
||||
|
||||
export const filterDatasetByName = (name: string): Dataset[] => {
|
||||
return searchResults
|
||||
.filter((r) => {
|
||||
return (r.entity as Dataset).name.indexOf(name) >= 0;
|
||||
})
|
||||
.map((r) => r.entity as Dataset);
|
||||
};
|
||||
|
||||
export const findDatasetByURN = (urn: string): Dataset => {
|
||||
return searchResults.find((r) => {
|
||||
return (r.entity as Dataset).urn === urn;
|
||||
})?.entity as Dataset;
|
||||
};
|
||||
|
||||
export const filterDatasetByPath = (path: string[]): Dataset[] => {
|
||||
return filterEntityByPath({ term: path.slice(-2).join('.'), searchResults }) as Dataset[];
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
export { datasetSearchResult } from './datasetSearchResult';
|
||||
export { dashboardSearchResult } from './dashboardSearchResult';
|
||||
export { dataFlowSearchResult } from './dataFlowSearchResult';
|
||||
export { dataJobSearchResult } from './dataJobSearchResult';
|
||||
export { chartSearchResult } from './chartSearchResult';
|
||||
export { userSearchResult } from './userSearchResult';
|
||||
@ -0,0 +1,54 @@
|
||||
/* eslint-disable prefer-object-spread */
|
||||
import { CorpUser, SearchResult, SearchResults } from '../../../types.generated';
|
||||
import { userEntity, UserEntityArg } from '../entity/userEntity';
|
||||
import { generateData } from './dataGenerator';
|
||||
|
||||
// login with one of these usernames
|
||||
const usernames = ['kafka', 'looker', 'datahub'];
|
||||
|
||||
type SearchResultArg = UserEntityArg;
|
||||
|
||||
const searchResult = (option?: SearchResultArg) => (): SearchResult => {
|
||||
return {
|
||||
entity: userEntity(option),
|
||||
matchedFields: [],
|
||||
__typename: 'SearchResult',
|
||||
};
|
||||
};
|
||||
|
||||
const generateSearchResults = (): SearchResult[] => {
|
||||
const loginUsers = usernames.map((username) => {
|
||||
return searchResult({ username })();
|
||||
});
|
||||
|
||||
return [...loginUsers, ...generateData<SearchResult>({ generator: searchResult(), count: 25 })];
|
||||
};
|
||||
|
||||
const searchResults = generateSearchResults();
|
||||
|
||||
export const userSearchResult: SearchResults = {
|
||||
start: 0,
|
||||
count: 0,
|
||||
total: 0,
|
||||
searchResults,
|
||||
facets: [],
|
||||
__typename: 'SearchResults',
|
||||
};
|
||||
|
||||
export const findUserByUsername = (username: string): CorpUser => {
|
||||
const result = searchResults.find((r) => {
|
||||
return (r.entity as CorpUser).username === username;
|
||||
});
|
||||
|
||||
return Object.assign({}, result?.entity as CorpUser);
|
||||
};
|
||||
|
||||
export const getUsers = (): CorpUser[] => {
|
||||
return searchResults.map((r) => Object.assign({}, r.entity as CorpUser));
|
||||
};
|
||||
|
||||
export const findUserByURN = (urn: string | null): CorpUser => {
|
||||
return searchResults.find((r) => {
|
||||
return (r.entity as CorpUser).urn === urn;
|
||||
})?.entity as CorpUser;
|
||||
};
|
||||
49
datahub-web-react/src/graphql-mock/fixtures/tag.ts
Normal file
49
datahub-web-react/src/graphql-mock/fixtures/tag.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import * as faker from 'faker';
|
||||
import { EntityType, Ownership, OwnershipType, Tag, TagUpdate } from '../../types.generated';
|
||||
import { getActor } from '../helper';
|
||||
import { findUserByURN } from './searchResult/userSearchResult';
|
||||
|
||||
export const tagDb: Tag[] = [];
|
||||
|
||||
export const generateTag = (ownership?: Ownership): Tag => {
|
||||
const name = `${faker.company.bsNoun()}`;
|
||||
const description = `${faker.commerce.productDescription()}`;
|
||||
const tag: Tag = {
|
||||
urn: `urn:li:tag:${name}`,
|
||||
name,
|
||||
description,
|
||||
type: EntityType.Tag,
|
||||
ownership,
|
||||
__typename: 'Tag',
|
||||
};
|
||||
|
||||
tagDb.push(tag);
|
||||
|
||||
return tag;
|
||||
};
|
||||
|
||||
export const createTag = ({ name, urn, description }: TagUpdate): Tag => {
|
||||
const user = findUserByURN(getActor());
|
||||
const tag: Tag = {
|
||||
urn,
|
||||
name,
|
||||
description,
|
||||
type: EntityType.Tag,
|
||||
ownership: {
|
||||
owners: [
|
||||
{
|
||||
owner: user,
|
||||
type: OwnershipType.Dataowner,
|
||||
__typename: 'Owner',
|
||||
},
|
||||
],
|
||||
lastModified: { time: Date.now(), __typename: 'AuditStamp' },
|
||||
__typename: 'Ownership',
|
||||
},
|
||||
__typename: 'Tag',
|
||||
};
|
||||
|
||||
tagDb.push(tag);
|
||||
|
||||
return tag;
|
||||
};
|
||||
21
datahub-web-react/src/graphql-mock/fixtures/user.ts
Normal file
21
datahub-web-react/src/graphql-mock/fixtures/user.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getUsers } from './searchResult/userSearchResult';
|
||||
|
||||
const createCorpUserSchema = ({ server, user }) => {
|
||||
const { info, editableInfo } = user;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete user.info;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete user.editableInfo;
|
||||
|
||||
const userSchema = server.create('CorpUser', user);
|
||||
userSchema.createInfo(info);
|
||||
userSchema.createEditableInfo(editableInfo);
|
||||
};
|
||||
|
||||
export const createLoginUsers = (server) => {
|
||||
const users = getUsers();
|
||||
users.forEach((user) => {
|
||||
createCorpUserSchema({ server, user });
|
||||
});
|
||||
};
|
||||
19
datahub-web-react/src/graphql-mock/helper.ts
Normal file
19
datahub-web-react/src/graphql-mock/helper.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { EntityType } from '../types.generated';
|
||||
|
||||
/**
|
||||
* Common helpers
|
||||
*/
|
||||
|
||||
export const getActor = (): string | null => {
|
||||
const cookie = new URLSearchParams(document.cookie.replaceAll('; ', '&'));
|
||||
return cookie.get('actor');
|
||||
};
|
||||
|
||||
export const toLowerCaseEntityType = (type: EntityType): string => {
|
||||
return type.toLowerCase().replace(/[_]/g, '');
|
||||
};
|
||||
|
||||
export const toTitleCase = (str: string): string => {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return `${str.charAt(0).toUpperCase()}${str.substr(1)}`.replace(/[\-_]/g, '');
|
||||
};
|
||||
105
datahub-web-react/src/graphql-mock/mutationHelper.ts
Normal file
105
datahub-web-react/src/graphql-mock/mutationHelper.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import {
|
||||
Chart,
|
||||
Dashboard,
|
||||
DataFlow,
|
||||
DataJob,
|
||||
Dataset,
|
||||
Entity,
|
||||
EntityType,
|
||||
GlobalTags,
|
||||
GlobalTagsUpdate,
|
||||
InstitutionalMemory,
|
||||
InstitutionalMemoryMetadata,
|
||||
InstitutionalMemoryUpdate,
|
||||
Owner,
|
||||
OwnerUpdate,
|
||||
TagAssociation,
|
||||
} from '../types.generated';
|
||||
import { findUserByURN } from './fixtures/searchResult/userSearchResult';
|
||||
import { tagDb } from './fixtures/tag';
|
||||
import { getActor } from './helper';
|
||||
|
||||
type UpdateEntityOwnersArg = {
|
||||
entity?: Entity;
|
||||
owners?: OwnerUpdate[];
|
||||
};
|
||||
|
||||
export const updateEntityOwners = ({ entity, owners }: UpdateEntityOwnersArg) => {
|
||||
const updateOwners = owners
|
||||
?.map((o) => {
|
||||
const user = findUserByURN(o.owner);
|
||||
return user
|
||||
? {
|
||||
owner: user,
|
||||
type: o.type,
|
||||
__typename: 'Owner',
|
||||
}
|
||||
: null;
|
||||
})
|
||||
.filter(Boolean) as Owner[];
|
||||
|
||||
const dataEntity = entity as Dataset | Chart | Dashboard | DataFlow | DataJob;
|
||||
if (dataEntity?.ownership?.owners) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
dataEntity.ownership.owners = updateOwners;
|
||||
}
|
||||
};
|
||||
|
||||
type UpdateEntityTagArg = {
|
||||
entity?: Entity;
|
||||
globalTags: GlobalTagsUpdate;
|
||||
};
|
||||
|
||||
export const updateEntityTag = ({ entity, globalTags }: UpdateEntityTagArg) => {
|
||||
const tagAssociations = globalTags.tags
|
||||
?.map((t) => {
|
||||
const tag = tagDb.find((ti) => {
|
||||
return ti.urn === t.tag.urn;
|
||||
});
|
||||
|
||||
return tag
|
||||
? {
|
||||
tag,
|
||||
__typename: 'TagAssociation',
|
||||
}
|
||||
: null;
|
||||
})
|
||||
.filter(Boolean) as TagAssociation[];
|
||||
const baseTags: TagAssociation[] = [];
|
||||
const baseGlobalTags: GlobalTags = {
|
||||
__typename: 'GlobalTags',
|
||||
tags: baseTags,
|
||||
};
|
||||
const dataEntity = entity as Dataset | Chart | Dashboard | DataFlow | DataJob;
|
||||
|
||||
dataEntity.globalTags = dataEntity.globalTags || baseGlobalTags;
|
||||
if (dataEntity.globalTags.tags) {
|
||||
dataEntity.globalTags.tags = tagAssociations;
|
||||
}
|
||||
};
|
||||
|
||||
type UpdateEntityLinkArg = {
|
||||
entity: Dataset;
|
||||
institutionalMemory: InstitutionalMemoryUpdate;
|
||||
};
|
||||
|
||||
export const updateEntityLink = ({ entity, institutionalMemory }: UpdateEntityLinkArg) => {
|
||||
const dataEntity = entity;
|
||||
const baseElements: InstitutionalMemoryMetadata[] = [];
|
||||
const baseInstitutionalMemory: InstitutionalMemory = {
|
||||
elements: baseElements,
|
||||
__typename: 'InstitutionalMemory',
|
||||
};
|
||||
|
||||
dataEntity.institutionalMemory = dataEntity.institutionalMemory || baseInstitutionalMemory;
|
||||
dataEntity.institutionalMemory.elements = institutionalMemory.elements.map((e) => {
|
||||
const link: InstitutionalMemoryMetadata = {
|
||||
__typename: 'InstitutionalMemoryMetadata',
|
||||
url: e.url,
|
||||
description: e.description as string,
|
||||
author: { urn: e.author, username: '', type: EntityType.CorpUser },
|
||||
created: { time: Date.now(), actor: getActor(), __typename: 'AuditStamp' },
|
||||
};
|
||||
return link;
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,152 @@
|
||||
import {
|
||||
AutoCompleteAllResults,
|
||||
AutoCompleteInput,
|
||||
AutoCompleteResultForEntity,
|
||||
Chart,
|
||||
CorpUser,
|
||||
Dashboard,
|
||||
DataFlow,
|
||||
DataJob,
|
||||
Dataset,
|
||||
EntityType,
|
||||
Maybe,
|
||||
} from '../../types.generated';
|
||||
import * as fixtures from '../fixtures';
|
||||
import { tagDb } from '../fixtures/tag';
|
||||
|
||||
type GetAutoCompleteAllResults = {
|
||||
data: {
|
||||
autoCompleteForAll: AutoCompleteAllResults;
|
||||
};
|
||||
};
|
||||
|
||||
const findSuggestions = ({ query, type }: AutoCompleteInput): AutoCompleteResultForEntity[] => {
|
||||
const q = query.toLowerCase().trim();
|
||||
|
||||
if (type === EntityType.Tag) {
|
||||
const results = q
|
||||
? tagDb.filter((t) => {
|
||||
return t.name.indexOf(q) >= 0;
|
||||
})
|
||||
: [];
|
||||
return [
|
||||
{
|
||||
type: EntityType.Tag,
|
||||
suggestions: results.map((r) => {
|
||||
return r.name;
|
||||
}),
|
||||
__typename: 'AutoCompleteResultForEntity',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const filterSearchResults = (): AutoCompleteResultForEntity[] => {
|
||||
const datasetResults = fixtures.datasetSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as Dataset).name?.toLowerCase()?.indexOf(q) ?? -1) >= 0;
|
||||
});
|
||||
const datasetQueryResults: Maybe<AutoCompleteResultForEntity> = datasetResults.length
|
||||
? {
|
||||
type: EntityType.Dataset,
|
||||
suggestions: datasetResults.map((r) => {
|
||||
return (r.entity as Dataset).name;
|
||||
}),
|
||||
__typename: 'AutoCompleteResultForEntity',
|
||||
}
|
||||
: null;
|
||||
|
||||
const dashboardResults = fixtures.dashboardSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as Dashboard).info?.name?.toLowerCase()?.indexOf(q) ?? -1) >= 0;
|
||||
});
|
||||
const dashboardQueryResults: Maybe<AutoCompleteResultForEntity> = dashboardResults.length
|
||||
? {
|
||||
type: EntityType.Dashboard,
|
||||
suggestions: dashboardResults.map((r) => {
|
||||
return (r.entity as Dashboard).info?.name || '';
|
||||
}),
|
||||
__typename: 'AutoCompleteResultForEntity',
|
||||
}
|
||||
: null;
|
||||
|
||||
const chartResults = fixtures.chartSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as Chart).info?.name?.toLowerCase()?.indexOf(q) ?? -1) >= 0;
|
||||
});
|
||||
const chartQueryResults: Maybe<AutoCompleteResultForEntity> = chartResults.length
|
||||
? {
|
||||
type: EntityType.Chart,
|
||||
suggestions: chartResults.map((r) => {
|
||||
return (r.entity as Chart).info?.name || '';
|
||||
}),
|
||||
__typename: 'AutoCompleteResultForEntity',
|
||||
}
|
||||
: null;
|
||||
|
||||
const dataFlowResults = fixtures.dataFlowSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as DataFlow).info?.name?.toLowerCase()?.indexOf(q) ?? -1) >= 0;
|
||||
});
|
||||
const dataFlowQueryResults: Maybe<AutoCompleteResultForEntity> = dataFlowResults.length
|
||||
? {
|
||||
type: EntityType.DataFlow,
|
||||
suggestions: dataFlowResults.map((r) => {
|
||||
return (r.entity as DataFlow).info?.name || '';
|
||||
}),
|
||||
__typename: 'AutoCompleteResultForEntity',
|
||||
}
|
||||
: null;
|
||||
|
||||
const dataJobResults = fixtures.dataJobSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as DataJob).info?.name?.toLowerCase()?.indexOf(q) ?? -1) >= 0;
|
||||
});
|
||||
const dataJobQueryResults: Maybe<AutoCompleteResultForEntity> = dataJobResults.length
|
||||
? {
|
||||
type: EntityType.DataJob,
|
||||
suggestions: dataJobResults.map((r) => {
|
||||
return (r.entity as DataJob).info?.name || '';
|
||||
}),
|
||||
__typename: 'AutoCompleteResultForEntity',
|
||||
}
|
||||
: null;
|
||||
|
||||
const userResults = fixtures.userSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as CorpUser).info?.fullName?.toLowerCase()?.indexOf(q) ?? -1) >= 0;
|
||||
});
|
||||
const userQueryResults: Maybe<AutoCompleteResultForEntity> = userResults.length
|
||||
? {
|
||||
type: EntityType.CorpUser,
|
||||
suggestions: userResults.map((r) => {
|
||||
return (r.entity as CorpUser).info?.fullName || '';
|
||||
}),
|
||||
__typename: 'AutoCompleteResultForEntity',
|
||||
}
|
||||
: null;
|
||||
|
||||
return [
|
||||
datasetQueryResults,
|
||||
dashboardQueryResults,
|
||||
chartQueryResults,
|
||||
dataFlowQueryResults,
|
||||
dataJobQueryResults,
|
||||
userQueryResults,
|
||||
].filter(Boolean) as AutoCompleteResultForEntity[];
|
||||
};
|
||||
|
||||
return q ? filterSearchResults() : [];
|
||||
};
|
||||
|
||||
export const getAutoCompleteAllResultsResolver = {
|
||||
getAutoCompleteAllResults({ variables: { input } }): GetAutoCompleteAllResults {
|
||||
const { query }: AutoCompleteInput = input;
|
||||
const suggestions = findSuggestions(input);
|
||||
|
||||
return {
|
||||
data: {
|
||||
autoCompleteForAll: {
|
||||
query,
|
||||
suggestions: suggestions.filter((s, i) => {
|
||||
return suggestions.indexOf(s) === i;
|
||||
}),
|
||||
__typename: 'AutoCompleteAllResults',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,94 @@
|
||||
import {
|
||||
AutoCompleteInput,
|
||||
AutoCompleteResults,
|
||||
Chart,
|
||||
CorpUser,
|
||||
Dashboard,
|
||||
DataFlow,
|
||||
DataJob,
|
||||
Dataset,
|
||||
EntityType,
|
||||
} from '../../types.generated';
|
||||
import * as fixtures from '../fixtures';
|
||||
import { tagDb } from '../fixtures/tag';
|
||||
|
||||
type GetAutoCompleteResults = {
|
||||
data: {
|
||||
autoComplete: AutoCompleteResults;
|
||||
};
|
||||
};
|
||||
|
||||
const findSuggestions = ({ query, type, field }: AutoCompleteInput): string[] => {
|
||||
const q = query.toLowerCase().trim();
|
||||
|
||||
if (type === EntityType.Tag) {
|
||||
const results = q
|
||||
? tagDb.filter((t) => {
|
||||
return t.name.indexOf(q) >= 0;
|
||||
})
|
||||
: [];
|
||||
return results.map((r) => {
|
||||
return r.name;
|
||||
});
|
||||
}
|
||||
|
||||
const filterSearchResults = () => {
|
||||
if (field === 'ldap') {
|
||||
return fixtures.userSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as CorpUser).username?.indexOf(q) ?? -1) >= 0;
|
||||
});
|
||||
}
|
||||
return [
|
||||
...fixtures.datasetSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as Dataset).name?.indexOf(q) ?? -1) >= 0;
|
||||
}),
|
||||
...fixtures.dashboardSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as Dashboard).info?.name?.indexOf(q) ?? -1) >= 0;
|
||||
}),
|
||||
...fixtures.chartSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as Chart).info?.name?.indexOf(q) ?? -1) >= 0;
|
||||
}),
|
||||
...fixtures.dataFlowSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as DataFlow).info?.name?.indexOf(q) ?? -1) >= 0;
|
||||
}),
|
||||
...fixtures.dataJobSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as DataJob).info?.name?.indexOf(q) ?? -1) >= 0;
|
||||
}),
|
||||
...fixtures.userSearchResult.searchResults.filter((r) => {
|
||||
return ((r.entity as CorpUser).info?.fullName?.toLowerCase()?.indexOf(q) ?? -1) >= 0;
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
const results = q ? filterSearchResults() : [];
|
||||
|
||||
return results
|
||||
.map((r) => {
|
||||
return field === 'ldap'
|
||||
? (r.entity as CorpUser)?.username
|
||||
: (r.entity as Dataset)?.name ||
|
||||
(r.entity as Dashboard | Chart | DataFlow | DataJob)?.info?.name ||
|
||||
(r.entity as CorpUser)?.info?.fullName ||
|
||||
'';
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
export const getAutoCompleteResultsResolver = {
|
||||
getAutoCompleteResults({ variables: { input } }): GetAutoCompleteResults {
|
||||
const { query }: AutoCompleteInput = input;
|
||||
const suggestions = findSuggestions(input);
|
||||
|
||||
return {
|
||||
data: {
|
||||
autoComplete: {
|
||||
query,
|
||||
suggestions: suggestions.filter((s, i) => {
|
||||
return suggestions.indexOf(s) === i;
|
||||
}),
|
||||
__typename: 'AutoCompleteResults',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
import { BrowsePath, BrowsePathsInput, EntityType } from '../../types.generated';
|
||||
|
||||
const paths = {
|
||||
[EntityType.Dataset](urn) {
|
||||
const result = urn.replace('urn:li:dataset:(urn:li:dataPlatform:', '').replace(')', '').split(',');
|
||||
return [result[result.length - 1].toLowerCase(), result[0], ...result[1].split('.')];
|
||||
},
|
||||
[EntityType.Dashboard](urn) {
|
||||
return urn.replace('urn:li:dashboard:(', '').replace(')', '').split(',');
|
||||
},
|
||||
[EntityType.Chart](urn) {
|
||||
return urn.replace('urn:li:chart:(', '').replace(')', '').split(',');
|
||||
},
|
||||
[EntityType.DataFlow](urn) {
|
||||
const result = urn.replace('urn:li:dataFlow:(', '').replace(')', '').split(',');
|
||||
return [result[0], result[result.length - 1].toLowerCase(), result[1]];
|
||||
},
|
||||
[EntityType.DataJob]() {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
type GetBrowsePaths = {
|
||||
data: {
|
||||
browsePaths: BrowsePath[];
|
||||
};
|
||||
};
|
||||
|
||||
export const getBrowsePathsResolver = {
|
||||
getBrowsePaths({ variables: { input } }): GetBrowsePaths {
|
||||
const { urn, type }: BrowsePathsInput = input;
|
||||
|
||||
return {
|
||||
data: {
|
||||
browsePaths: [
|
||||
{
|
||||
path: paths[type](urn),
|
||||
__typename: 'BrowsePath',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
import * as fixtures from '../fixtures';
|
||||
import { BrowseInput } from '../../types.generated';
|
||||
import { EntityBrowseFn, GetBrowseResults } from '../types';
|
||||
import { toLowerCaseEntityType, toTitleCase } from '../helper';
|
||||
|
||||
const toPathTitle = (paths: string[]): string => {
|
||||
return paths?.map((p) => toTitleCase(p)).join('');
|
||||
};
|
||||
|
||||
export const getBrowseResultsResolver = {
|
||||
getBrowseResults({ variables: { input } }): GetBrowseResults {
|
||||
const { type, path = [], start = 0, count = 0 }: BrowseInput = input;
|
||||
const startValue = start as number;
|
||||
const countValue = count as number;
|
||||
const paths = path as string[];
|
||||
const entityType = toLowerCaseEntityType(type);
|
||||
const pathTitle = toPathTitle(paths);
|
||||
|
||||
const result: GetBrowseResults | EntityBrowseFn =
|
||||
fixtures[`${entityType}BrowseResult`][`${entityType}Browse${pathTitle}`];
|
||||
|
||||
if (typeof result === 'function') {
|
||||
return result({ start: startValue, count: countValue, path: paths });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import { Chart } from '../../types.generated';
|
||||
import { findChartByURN } from '../fixtures/searchResult/chartSearchResult';
|
||||
|
||||
type GetChart = {
|
||||
data: {
|
||||
chart: Chart;
|
||||
};
|
||||
};
|
||||
|
||||
export const getChartResolver = {
|
||||
getChart({ variables: { urn } }): GetChart {
|
||||
const chart = findChartByURN(urn) as Chart;
|
||||
return {
|
||||
data: {
|
||||
chart: Object.assign(chart, {
|
||||
info: {
|
||||
...chart.info,
|
||||
inputs: [],
|
||||
customProperties: [],
|
||||
lastRefreshed: null,
|
||||
created: {
|
||||
time: 1619160920,
|
||||
__typename: 'AuditStamp',
|
||||
},
|
||||
},
|
||||
query: null,
|
||||
downstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'DownstreamEntityRelationships',
|
||||
},
|
||||
upstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'UpstreamEntityRelationships',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,36 @@
|
||||
import { Dashboard } from '../../types.generated';
|
||||
import { findDashboardByURN } from '../fixtures/searchResult/dashboardSearchResult';
|
||||
|
||||
type GetDashboard = {
|
||||
data: { dashboard: Dashboard };
|
||||
};
|
||||
|
||||
export const getDashboardResolver = {
|
||||
getDashboard({ variables: { urn } }): GetDashboard {
|
||||
const dashboard = findDashboardByURN(urn) as Dashboard;
|
||||
return {
|
||||
data: {
|
||||
dashboard: Object.assign(dashboard, {
|
||||
info: {
|
||||
...dashboard.info,
|
||||
charts: [],
|
||||
customProperties: [],
|
||||
lastRefreshed: null,
|
||||
created: {
|
||||
time: 1619160920,
|
||||
__typename: 'AuditStamp',
|
||||
},
|
||||
},
|
||||
downstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'DownstreamEntityRelationships',
|
||||
},
|
||||
upstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'UpstreamEntityRelationships',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { DataFlow } from '../../types.generated';
|
||||
import { findDataFlowByURN } from '../fixtures/searchResult/dataFlowSearchResult';
|
||||
|
||||
type GetDataFlow = {
|
||||
data: { dataFlow: DataFlow };
|
||||
};
|
||||
|
||||
export const getDataFlowResolver = {
|
||||
getDataFlow({ variables: { urn } }): GetDataFlow {
|
||||
const dataFlow = findDataFlowByURN(urn) as DataFlow;
|
||||
return {
|
||||
data: {
|
||||
dataFlow: Object.assign(dataFlow, {
|
||||
info: {
|
||||
...dataFlow.info,
|
||||
externalUrl: 'https://airflow.demo.datahubproject.io/tree?dag_id=datahub_analytics_refresh',
|
||||
inputs: [],
|
||||
customProperties: [],
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import { DataJob } from '../../types.generated';
|
||||
import { findDataJobByURN } from '../fixtures/searchResult/dataJobSearchResult';
|
||||
|
||||
type GetJobFlow = {
|
||||
data: { dataJob: DataJob };
|
||||
};
|
||||
|
||||
export const getDataJobResolver = {
|
||||
getDataJob({ variables: { urn } }): GetJobFlow {
|
||||
const dataJob = findDataJobByURN(urn) as DataJob;
|
||||
return {
|
||||
data: {
|
||||
dataJob: Object.assign(dataJob, {
|
||||
info: {
|
||||
...dataJob.info,
|
||||
externalUrl: 'https://airflow.demo.datahubproject.io/tree?dag_id=datahub_analytics_refresh',
|
||||
inputs: [],
|
||||
customProperties: [],
|
||||
},
|
||||
downstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'DownstreamEntityRelationships',
|
||||
},
|
||||
upstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'UpstreamEntityRelationships',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,43 @@
|
||||
import { Dataset, InstitutionalMemory, InstitutionalMemoryMetadata } from '../../types.generated';
|
||||
import { findDatasetByURN } from '../fixtures/searchResult/datasetSearchResult';
|
||||
|
||||
type GetDataset = {
|
||||
data: { dataset: Dataset };
|
||||
};
|
||||
|
||||
export const getDatasetResolver = {
|
||||
getDataset({ variables: { urn } }): GetDataset {
|
||||
const dataset = findDatasetByURN(urn);
|
||||
|
||||
if (!dataset.institutionalMemory) {
|
||||
const baseElements: InstitutionalMemoryMetadata[] = [];
|
||||
const baseInstitutionalMemory: InstitutionalMemory = {
|
||||
elements: baseElements,
|
||||
__typename: 'InstitutionalMemory',
|
||||
};
|
||||
dataset.institutionalMemory = baseInstitutionalMemory;
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
dataset: Object.assign(dataset, {
|
||||
schema: null,
|
||||
editableProperties: null,
|
||||
editableSchemaMetadata: null,
|
||||
deprecation: null,
|
||||
downstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'DownstreamEntityRelationships',
|
||||
},
|
||||
upstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'UpstreamEntityRelationships',
|
||||
},
|
||||
glossaryTerms: null,
|
||||
institutionalMemory: null,
|
||||
usageStats: null,
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,114 @@
|
||||
import * as fixtures from '../fixtures';
|
||||
import {
|
||||
Chart,
|
||||
CorpUser,
|
||||
Dashboard,
|
||||
DataFlow,
|
||||
DataJob,
|
||||
Dataset,
|
||||
EntityType,
|
||||
SearchInput,
|
||||
SearchResult,
|
||||
SearchResults,
|
||||
} from '../../types.generated';
|
||||
|
||||
const entitySearchResults = {
|
||||
[EntityType.Dataset]: fixtures.datasetSearchResult,
|
||||
[EntityType.Dashboard]: fixtures.dashboardSearchResult,
|
||||
[EntityType.Chart]: fixtures.chartSearchResult,
|
||||
[EntityType.CorpUser]: fixtures.userSearchResult,
|
||||
[EntityType.DataFlow]: fixtures.dataFlowSearchResult,
|
||||
[EntityType.DataJob]: fixtures.dataJobSearchResult,
|
||||
};
|
||||
|
||||
type FindByQueryArg = {
|
||||
query: string;
|
||||
searchResults: SearchResult[];
|
||||
start: number;
|
||||
count: number;
|
||||
};
|
||||
|
||||
type QueryResult = {
|
||||
results: SearchResult[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
const findByQuery = ({ query, searchResults, start, count }: FindByQueryArg): QueryResult => {
|
||||
if (query === '*') {
|
||||
const results = searchResults.slice(start, start + count);
|
||||
|
||||
return { results, total: results.length };
|
||||
}
|
||||
|
||||
if (query.indexOf('owners:') >= 0) {
|
||||
const [, ownerQuery] = query.split(':');
|
||||
const results = searchResults.filter((r) => {
|
||||
return (r.entity as Dataset | Dashboard | Chart | DataFlow | DataJob).ownership?.owners?.filter((o) => {
|
||||
return (o.owner as CorpUser).username === ownerQuery;
|
||||
}).length;
|
||||
});
|
||||
|
||||
return { results, total: results.length };
|
||||
}
|
||||
|
||||
if (query.indexOf('tags:') >= 0) {
|
||||
const [, tagQuery] = query.split(':');
|
||||
const results = searchResults.filter((r) => {
|
||||
return (r.entity as Dataset | Dashboard | Chart | DataFlow | DataJob).globalTags?.tags?.filter((t) => {
|
||||
return t.tag.name === tagQuery;
|
||||
}).length;
|
||||
});
|
||||
|
||||
return { results, total: results.length };
|
||||
}
|
||||
|
||||
const results = [
|
||||
...searchResults.filter((r) => {
|
||||
return ((r.entity as Dataset).name?.indexOf(query) ?? -1) >= 0;
|
||||
}),
|
||||
...searchResults.filter((r) => {
|
||||
return ((r.entity as Dashboard | Chart | DataFlow | DataJob).info?.name?.indexOf(query) ?? -1) >= 0;
|
||||
}),
|
||||
...searchResults.filter((r) => {
|
||||
return ((r.entity as CorpUser).info?.fullName?.toLowerCase()?.indexOf(query) ?? -1) >= 0;
|
||||
}),
|
||||
];
|
||||
|
||||
return { results, total: results.length };
|
||||
};
|
||||
|
||||
type GetSearchResults = {
|
||||
data: {
|
||||
search: SearchResults;
|
||||
};
|
||||
} | null;
|
||||
|
||||
export const getSearchResultsResolver = {
|
||||
getSearchResults({ variables: { input } }): GetSearchResults {
|
||||
const { type, start = 0, count = 10, query }: SearchInput = input;
|
||||
const startValue = start as number;
|
||||
const countValue = count as number;
|
||||
const entitySearchResult: SearchResults = entitySearchResults[type];
|
||||
const { results, total } = findByQuery({
|
||||
query: query.toLowerCase(),
|
||||
searchResults: entitySearchResult.searchResults,
|
||||
start: startValue,
|
||||
count: countValue,
|
||||
});
|
||||
|
||||
return entitySearchResult
|
||||
? {
|
||||
data: {
|
||||
search: {
|
||||
start: startValue,
|
||||
count: results.length,
|
||||
total,
|
||||
searchResults: results,
|
||||
facets: entitySearchResult.facets,
|
||||
__typename: entitySearchResult.__typename,
|
||||
},
|
||||
},
|
||||
}
|
||||
: null;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { Tag } from '../../types.generated';
|
||||
import { tagDb } from '../fixtures/tag';
|
||||
|
||||
type GetTag = {
|
||||
data: { tag: Tag | undefined };
|
||||
};
|
||||
|
||||
export const getTagResolver = {
|
||||
getTag({ variables: { urn } }): GetTag {
|
||||
const tag = tagDb.find((t) => {
|
||||
return t.urn === urn;
|
||||
});
|
||||
|
||||
if (tag && !tag?.ownership) {
|
||||
tag.ownership = null;
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
tag,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
44
datahub-web-react/src/graphql-mock/resolver/index.ts
Normal file
44
datahub-web-react/src/graphql-mock/resolver/index.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { getBrowseResultsResolver } from './getBrowseResultsResolver';
|
||||
import { getSearchResultsResolver } from './getSearchResultsResolver';
|
||||
import { getAutoCompleteAllResultsResolver } from './getAutoCompleteAllResultsResolver';
|
||||
import { getAutoCompleteResultsResolver } from './getAutoCompleteResultsResolver';
|
||||
import { getBrowsePathsResolver } from './getBrowsePathsResolver';
|
||||
import { getDatasetResolver } from './getDatasetResolver';
|
||||
import { getDashboardResolver } from './getDashboardResolver';
|
||||
import { getChartResolver } from './getChartResolver';
|
||||
import { getDataFlowResolver } from './getDataFlowResolver';
|
||||
import { getDataJobResolver } from './getDataJobResolver';
|
||||
import { getTagResolver } from './getTagResolver';
|
||||
import { isAnalyticsEnabledResolver } from './isAnalyticsEnabledResolver';
|
||||
import { updateDatasetResolver } from './updateDatasetResolver';
|
||||
import { updateDashboardResolver } from './updateDashboardResolver';
|
||||
import { updateChartResolver } from './updateChartResolver';
|
||||
import { updateDataFlowResolver } from './updateDataFlowResolver';
|
||||
import { updateDataJobResolver } from './updateDataJobResolver';
|
||||
import { updateTagResolver } from './updateTagResolver';
|
||||
|
||||
const resolver = {
|
||||
...getSearchResultsResolver,
|
||||
...getBrowseResultsResolver,
|
||||
...getAutoCompleteAllResultsResolver,
|
||||
...getAutoCompleteResultsResolver,
|
||||
...getBrowsePathsResolver,
|
||||
...getDatasetResolver,
|
||||
...getDashboardResolver,
|
||||
...getChartResolver,
|
||||
...getDataFlowResolver,
|
||||
...getDataJobResolver,
|
||||
...getTagResolver,
|
||||
...isAnalyticsEnabledResolver,
|
||||
...updateDatasetResolver,
|
||||
...updateDashboardResolver,
|
||||
...updateChartResolver,
|
||||
...updateDataFlowResolver,
|
||||
...updateDataJobResolver,
|
||||
...updateTagResolver,
|
||||
};
|
||||
|
||||
export const resolveRequest = (schema, request) => {
|
||||
const { operationName, variables } = JSON.parse(request.requestBody);
|
||||
return resolver[operationName] && resolver[operationName]({ schema, variables });
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export const isAnalyticsEnabledResolver = {
|
||||
isAnalyticsEnabled(): boolean {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,46 @@
|
||||
import { Chart, ChartUpdateInput } from '../../types.generated';
|
||||
import { findChartByURN } from '../fixtures/searchResult/chartSearchResult';
|
||||
import { updateEntityOwners, updateEntityTag } from '../mutationHelper';
|
||||
|
||||
type UpdateChart = {
|
||||
data: { updateChart: Chart };
|
||||
};
|
||||
|
||||
export const updateChartResolver = {
|
||||
updateChart({ variables: { input } }): UpdateChart {
|
||||
const { globalTags, urn, ownership }: ChartUpdateInput = input;
|
||||
const chart = findChartByURN(urn);
|
||||
|
||||
if (ownership) {
|
||||
updateEntityOwners({ entity: chart, owners: ownership?.owners });
|
||||
} else if (globalTags) {
|
||||
updateEntityTag({ entity: chart, globalTags });
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
updateChart: Object.assign(chart, {
|
||||
info: {
|
||||
...chart.info,
|
||||
inputs: [],
|
||||
customProperties: [],
|
||||
lastRefreshed: null,
|
||||
created: {
|
||||
time: 1619160920,
|
||||
__typename: 'AuditStamp',
|
||||
},
|
||||
},
|
||||
query: null,
|
||||
downstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'DownstreamEntityRelationships',
|
||||
},
|
||||
upstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'UpstreamEntityRelationships',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,45 @@
|
||||
import { Dashboard, DashboardUpdateInput } from '../../types.generated';
|
||||
import { findDashboardByURN } from '../fixtures/searchResult/dashboardSearchResult';
|
||||
import { updateEntityOwners, updateEntityTag } from '../mutationHelper';
|
||||
|
||||
type UpdateDashboard = {
|
||||
data: { updateDashboard: Dashboard };
|
||||
};
|
||||
|
||||
export const updateDashboardResolver = {
|
||||
updateDashboard({ variables: { input } }): UpdateDashboard {
|
||||
const { urn, ownership, globalTags }: DashboardUpdateInput = input;
|
||||
const dashboard = findDashboardByURN(urn);
|
||||
|
||||
if (ownership) {
|
||||
updateEntityOwners({ entity: dashboard, owners: ownership?.owners });
|
||||
} else if (globalTags) {
|
||||
updateEntityTag({ entity: dashboard, globalTags });
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
updateDashboard: Object.assign(dashboard, {
|
||||
info: {
|
||||
...dashboard.info,
|
||||
charts: [],
|
||||
customProperties: [],
|
||||
lastRefreshed: null,
|
||||
created: {
|
||||
time: 1619160920,
|
||||
__typename: 'AuditStamp',
|
||||
},
|
||||
},
|
||||
downstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'DownstreamEntityRelationships',
|
||||
},
|
||||
upstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'UpstreamEntityRelationships',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
import { DataFlow, DataFlowUpdateInput } from '../../types.generated';
|
||||
import { findDataFlowByURN } from '../fixtures/searchResult/dataFlowSearchResult';
|
||||
import { updateEntityOwners, updateEntityTag } from '../mutationHelper';
|
||||
|
||||
type UpdateDataFlow = {
|
||||
data: { updateDataFlow: DataFlow };
|
||||
};
|
||||
|
||||
export const updateDataFlowResolver = {
|
||||
updateDataFlow({ variables: { input } }): UpdateDataFlow {
|
||||
const { globalTags, urn, ownership }: DataFlowUpdateInput = input;
|
||||
const dataFlow = findDataFlowByURN(urn);
|
||||
|
||||
if (ownership) {
|
||||
updateEntityOwners({ entity: dataFlow, owners: ownership?.owners });
|
||||
} else if (globalTags) {
|
||||
updateEntityTag({ entity: dataFlow, globalTags });
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
updateDataFlow: Object.assign(dataFlow, {
|
||||
info: {
|
||||
...dataFlow.info,
|
||||
externalUrl: 'https://airflow.demo.datahubproject.io/tree?dag_id=datahub_analytics_refresh',
|
||||
inputs: [],
|
||||
customProperties: [],
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { DataJob, DataJobUpdateInput } from '../../types.generated';
|
||||
import { findDataJobByURN } from '../fixtures/searchResult/dataJobSearchResult';
|
||||
import { updateEntityOwners, updateEntityTag } from '../mutationHelper';
|
||||
|
||||
type UpdateDataJob = {
|
||||
data: { updateDataJob: DataJob };
|
||||
};
|
||||
|
||||
export const updateDataJobResolver = {
|
||||
updateDataJob({ variables: { input } }): UpdateDataJob {
|
||||
const { urn, ownership, globalTags }: DataJobUpdateInput = input;
|
||||
const dataJob = findDataJobByURN(urn);
|
||||
|
||||
if (ownership) {
|
||||
updateEntityOwners({ entity: dataJob, owners: ownership?.owners });
|
||||
} else if (globalTags) {
|
||||
updateEntityTag({ entity: dataJob, globalTags });
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
updateDataJob: dataJob,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
import { Dataset, DatasetUpdateInput } from '../../types.generated';
|
||||
import { findDatasetByURN } from '../fixtures/searchResult/datasetSearchResult';
|
||||
import { updateEntityLink, updateEntityOwners, updateEntityTag } from '../mutationHelper';
|
||||
|
||||
type UpdateDataset = {
|
||||
data: { updateDataset: Dataset };
|
||||
};
|
||||
|
||||
export const updateDatasetResolver = {
|
||||
updateDataset({ variables: { input } }): UpdateDataset {
|
||||
const { urn, ownership, globalTags, institutionalMemory }: DatasetUpdateInput = input;
|
||||
const dataset = findDatasetByURN(urn);
|
||||
|
||||
if (ownership) {
|
||||
updateEntityOwners({ entity: dataset, owners: ownership.owners });
|
||||
} else if (globalTags) {
|
||||
updateEntityTag({ entity: dataset, globalTags });
|
||||
} else if (institutionalMemory) {
|
||||
updateEntityLink({ entity: dataset, institutionalMemory });
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
updateDataset: Object.assign(dataset, {
|
||||
schema: null,
|
||||
editableSchemaMetadata: null,
|
||||
deprecation: null,
|
||||
downstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'DownstreamEntityRelationships',
|
||||
},
|
||||
upstreamLineage: {
|
||||
entities: [],
|
||||
__typename: 'UpstreamEntityRelationships',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { Tag } from '../../types.generated';
|
||||
import { createTag } from '../fixtures/tag';
|
||||
|
||||
type UpdateTag = {
|
||||
data: { updateTag: Tag | undefined };
|
||||
};
|
||||
|
||||
export const updateTagResolver = {
|
||||
updateTag({ variables: { input } }): UpdateTag {
|
||||
const tag: Tag = createTag(input);
|
||||
|
||||
return {
|
||||
data: {
|
||||
updateTag: tag,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
14
datahub-web-react/src/graphql-mock/schema.ts
Normal file
14
datahub-web-react/src/graphql-mock/schema.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { loader } from 'graphql.macro';
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
import { buildASTSchema } from 'graphql';
|
||||
|
||||
const gmsSchema = loader('../../../datahub-graphql-core/src/main/resources/gms.graphql');
|
||||
const feSchema = loader('../../../datahub-frontend/conf/datahub-frontend.graphql');
|
||||
|
||||
const graphQLSchemaAST = gql`
|
||||
${gmsSchema}
|
||||
${feSchema}
|
||||
`;
|
||||
|
||||
export const graphQLSchema = buildASTSchema(graphQLSchemaAST);
|
||||
83
datahub-web-react/src/graphql-mock/server.ts
Normal file
83
datahub-web-react/src/graphql-mock/server.ts
Normal file
@ -0,0 +1,83 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { Model, Response, createServer, belongsTo } from 'miragejs';
|
||||
import { createGraphQLHandler } from '@miragejs/graphql';
|
||||
|
||||
import { graphQLSchema } from './schema';
|
||||
import { GlobalCfg } from '../conf';
|
||||
|
||||
import { resolveRequest } from './resolver';
|
||||
import { createLoginUsers } from './fixtures/user';
|
||||
import { findUserByURN } from './fixtures/searchResult/userSearchResult';
|
||||
|
||||
export function makeServer(environment = 'development') {
|
||||
return createServer({
|
||||
environment,
|
||||
models: {
|
||||
CorpUser: Model.extend({
|
||||
info: belongsTo('CorpUserInfo'),
|
||||
editableInfo: belongsTo('CorpUserEditableInfo'),
|
||||
}),
|
||||
},
|
||||
|
||||
seeds(server) {
|
||||
createLoginUsers(server);
|
||||
|
||||
console.log(server.db.dump());
|
||||
},
|
||||
|
||||
routes() {
|
||||
const graphQLHandler = createGraphQLHandler(graphQLSchema, this.schema);
|
||||
|
||||
this.post('/api/v2/graphql', (schema, request) => {
|
||||
return resolveRequest(schema, request) ?? graphQLHandler(schema, request);
|
||||
});
|
||||
|
||||
this.get('/authenticate', () => new Response(200));
|
||||
|
||||
this.post('/logIn', (_schema, request) => {
|
||||
const payload = JSON.parse(request.requestBody);
|
||||
const cookieExpiration = new Date(Date.now() + 24 * 3600 * 1000);
|
||||
const urn = `urn:li:corpuser:${payload.username}`;
|
||||
const user = findUserByURN(urn);
|
||||
|
||||
if (!user) {
|
||||
return new Response(404);
|
||||
}
|
||||
|
||||
document.cookie = `${
|
||||
GlobalCfg.CLIENT_AUTH_COOKIE
|
||||
}=${urn}; domain=localhost; path=/; expires=${cookieExpiration.toUTCString()};`;
|
||||
|
||||
return new Response(200);
|
||||
});
|
||||
|
||||
this.post('/track', () => new Response(200));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function makeServerForCypress() {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (window.Cypress) {
|
||||
const otherDomains = [];
|
||||
const methods = ['head', 'get', 'put', 'patch', 'post', 'delete'];
|
||||
|
||||
createServer({
|
||||
environment: 'test',
|
||||
|
||||
routes() {
|
||||
for (const domain of ['/*', ...otherDomains]) {
|
||||
for (const method of methods) {
|
||||
this[method](`${domain}`, async (_schema, request) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const [status, headers, body] = await window.handleFromCypress(request);
|
||||
return new Response(status, headers, body);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
36
datahub-web-react/src/graphql-mock/types.ts
Normal file
36
datahub-web-react/src/graphql-mock/types.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { BrowseResults } from '../types.generated';
|
||||
|
||||
export type DataSchema = {
|
||||
id?: string | undefined;
|
||||
attrs: AnyRecord;
|
||||
modelName: string;
|
||||
save(): void;
|
||||
update<K extends never>(key: K, value: AnyRecord[K]): void;
|
||||
update(changes: Partial<AnyRecord>): void;
|
||||
destroy(): void;
|
||||
reload(): void;
|
||||
};
|
||||
|
||||
export type AnyRecord = Record<string, unknown>;
|
||||
|
||||
export type EntityBrowsePath = {
|
||||
name: string;
|
||||
paths: EntityBrowsePath[];
|
||||
count?: number;
|
||||
};
|
||||
|
||||
export type StringNumber = string | number;
|
||||
|
||||
export type GetBrowseResults = {
|
||||
data: {
|
||||
browse: BrowseResults;
|
||||
};
|
||||
};
|
||||
|
||||
type EntityBrowseFnArg = {
|
||||
start: number;
|
||||
count: number;
|
||||
path: string[];
|
||||
};
|
||||
|
||||
export type EntityBrowseFn = (arg: EntityBrowseFnArg) => GetBrowseResults;
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import './graphql-mock/createServer';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
|
||||
24
datahub-web-react/src/setupProxy.js
Normal file
24
datahub-web-react/src/setupProxy.js
Normal file
@ -0,0 +1,24 @@
|
||||
if (process.env.REACT_APP_MOCK === 'true' || process.env.REACT_APP_MOCK === 'cy') {
|
||||
// no proxy needed, MirageJS will intercept all http requests
|
||||
module.exports = function () {};
|
||||
} else {
|
||||
// create a proxy to the graphql server running in docker container
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
|
||||
module.exports = function (app) {
|
||||
app.use(
|
||||
'/logIn',
|
||||
createProxyMiddleware({
|
||||
target: 'http://localhost:9002',
|
||||
changeOrigin: true,
|
||||
}),
|
||||
);
|
||||
app.use(
|
||||
'/api/v2/graphql',
|
||||
createProxyMiddleware({
|
||||
target: 'http://localhost:9002',
|
||||
changeOrigin: true,
|
||||
}),
|
||||
);
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user