Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add feature discovery route and page #1185

Merged
merged 5 commits into from
Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 7 additions & 20 deletions frontend/amundsen_application/static/.betterer.results
Original file line number Diff line number Diff line change
Expand Up @@ -505,17 +505,14 @@ exports[`eslint`] = {
"js/components/Tags/index.tsx:3468508233": [
[38, 4, 21, "Must use destructuring props assignment", "4236634811"]
],
"js/config/config-default.ts:1497564975": [
"js/config/config-default.ts:54755128": [
[2, 0, 72, "\`../interfaces\` import should occur before import of \`./config-types\`", "1449508543"],
[236, 6, 21, "\'partitionKey\' is defined but never used.", "399589312"],
[237, 6, 23, "\'partitionValue\' is defined but never used.", "793372348"]
[281, 6, 21, "\'partitionKey\' is defined but never used.", "399589312"],
[282, 6, 23, "\'partitionValue\' is defined but never used.", "793372348"]
],
"js/config/config-utils.ts:2658622130": [
[11, 0, 45, "\`../interfaces\` import should occur before import of \`./config-types\`", "3885176344"]
],
"js/ducks/announcements/api/v0.ts:908063915": [
[30, 13, 64, "Expected the Promise rejection reason to be an Error.", "3665078104"]
],
"js/ducks/announcements/index.spec.ts:1898496537": [
[3, 0, 148, "\`.\` import should occur after import of \`./types\`", "4154971894"]
],
Expand All @@ -542,8 +539,7 @@ exports[`eslint`] = {
[0, 16, 13, "\'AxiosResponse\' is defined but never used.", "1743879434"]
],
"js/ducks/dashboard/api/v0.ts:2755958463": [
[38, 28, 104, "Do not nest ternary expressions.", "1163212497"],
[45, 13, 68, "Expected the Promise rejection reason to be an Error.", "2764124112"]
[38, 28, 104, "Do not nest ternary expressions.", "1163212497"]
],
"js/ducks/issue/reducer.ts:1774302197": [
[119, 6, 56, "Unexpected lexical declaration in case block.", "2031834906"]
Expand All @@ -557,16 +553,9 @@ exports[`eslint`] = {
[250, 8, 31, "Use object destructuring.", "507617405"],
[251, 8, 45, "Use object destructuring.", "244310461"]
],
"js/ducks/lastIndexed/api/v0.ts:100101993": [
[18, 13, 44, "Expected the Promise rejection reason to be an Error.", "107682302"]
],
"js/ducks/lastIndexed/sagas.ts:1498244597": [
[7, 2, 29, "\'action\' is defined but never used.", "566797395"]
],
"js/ducks/lineage/api/v0.ts:63450243": [
[19, 13, 26, "Expected the Promise rejection reason to be an Error.", "875716520"],
[44, 13, 26, "Expected the Promise rejection reason to be an Error.", "875716520"]
],
"js/ducks/middlewares/analyticsMiddleware.ts:3943673455": [
[11, 7, 8, "\'getState\' is defined but never used.", "1919118020"]
],
Expand Down Expand Up @@ -594,9 +583,7 @@ exports[`eslint`] = {
],
"js/ducks/tableMetadata/api/v0.ts:1722284731": [
[75, 8, 23, "Use object destructuring.", "1142306891"],
[78, 13, 39, "Expected the Promise rejection reason to be an Error.", "3871091426"],
[125, 23, -4008, "Expected to return a value at the end of arrow function.", "5381"],
[181, 13, 32, "Expected the Promise rejection reason to be an Error.", "2773728948"]
[125, 23, -4008, "Expected to return a value at the end of arrow function.", "5381"]
],
"js/ducks/tableMetadata/index.spec.ts:2466548220": [
[480, 22, 11, "\'mockSuccess\' is already declared in the upper scope.", "1120045516"],
Expand Down Expand Up @@ -1003,15 +990,15 @@ exports[`eslint`] = {
[208, 14, 151, "A control must be associated with a text label.", "1015004405"],
[218, 12, 432, "A control must be associated with a text label.", "534436217"]
],
"js/pages/TableDetailPage/SourceLink/index.spec.tsx:4268867941": [
"js/pages/TableDetailPage/SourceLink/index.spec.tsx:3686659452": [
[19, 55, 10, "Prop spreading is forbidden", "480399587"]
],
"js/pages/TableDetailPage/TableHeaderBullets/index.spec.tsx:3978961134": [
[45, 28, 10, "Prop spreading is forbidden", "480399587"],
[91, 6, 25, "Use object destructuring.", "354229464"],
[92, 6, 29, "Use object destructuring.", "2645724888"]
],
"js/pages/TableDetailPage/TableHeaderBullets/index.tsx:3687188386": [
"js/pages/TableDetailPage/TableHeaderBullets/index.tsx:1080737806": [
[35, 25, 19, "Must use destructuring props assignment", "2861102024"],
[40, 4, 25, "Must use destructuring props assignment", "2288791878"],
[45, 6, 17, "Must use destructuring props assignment", "2741866010"],
Expand Down
1 change: 0 additions & 1 deletion frontend/amundsen_application/static/.betterer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export default {
'no-useless-return': 'error',
'no-void': 'error',
'prefer-destructuring': 'error',
'prefer-promise-reject-errors': 'error',
'react/button-has-type': 'error',
'react/destructuring-assignment': 'error',
'react/jsx-boolean-value': 'error',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,47 @@ const configDefault: AppConfig = {
],
notices: {},
},
[ResourceType.feature]: {
displayName: 'ML Features',
supportedSources: {
bigquery: {
displayName: 'BigQuery',
iconClass: 'icon-bigquery',
},
delta: {
displayName: 'Delta',
iconClass: 'icon-delta',
},
dremio: {
displayName: 'Dremio',
iconClass: 'icon-dremio',
},
druid: {
displayName: 'Druid',
iconClass: 'icon-druid',
},
hive: {
displayName: 'Hive',
iconClass: 'icon-hive',
},
presto: {
displayName: 'Presto',
iconClass: 'icon-presto',
},
postgres: {
displayName: 'Postgres',
iconClass: 'icon-postgres',
},
redshift: {
displayName: 'Redshift',
iconClass: 'icon-redshift',
},
snowflake: {
displayName: 'Snowflake',
iconClass: 'icon-snowflake',
},
},
},
[ResourceType.table]: {
displayName: 'Datasets',
supportedSources: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ interface ResourceConfig {
[ResourceType.dashboard]: BaseResourceConfig;
[ResourceType.table]: TableResourceConfig;
[ResourceType.user]: BaseResourceConfig;
[ResourceType.feature]: BaseResourceConfig;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import axios from 'axios';

import { featureMetadata } from 'fixtures/metadata/feature';

import * as API from './v0';

jest.mock('axios');

describe('getFeature', () => {
let axiosMockGet;
it('resolves with object containing feature metadata and status code', async () => {
const mockStatus = 200;
const mockResponse = {
data: {
featureData: featureMetadata,
msg: 'success',
},
status: mockStatus,
};
axiosMockGet = jest
.spyOn(axios, 'get')
.mockImplementationOnce(() => Promise.resolve(mockResponse));
expect.assertions(2);
await API.getFeature('testUri').then((processedResponse) => {
expect(processedResponse).toEqual({
feature: featureMetadata,
statusCode: mockStatus,
});
});
expect(axiosMockGet).toHaveBeenCalled();
});

it('catches error and resolves with object containing error information', async () => {
const mockStatus = 500;
const mockMessage = 'oops';
const mockResponse = {
response: {
data: {
msg: mockMessage,
},
status: mockStatus,
},
};
axiosMockGet = jest
.spyOn(axios, 'get')
.mockImplementationOnce(() => Promise.reject(mockResponse));
expect.assertions(2);
await API.getFeature('testUri').catch((processedResponse) => {
expect(processedResponse).toEqual({
statusMessage: mockMessage,
statusCode: mockStatus,
});
});
expect(axiosMockGet).toHaveBeenCalled();
});
});
33 changes: 33 additions & 0 deletions frontend/amundsen_application/static/js/ducks/feature/api/v0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios, { AxiosResponse } from 'axios';
import * as qs from 'simple-query-string';

import { FeatureMetadata } from 'interfaces/Feature';

export type GetFeatureAPI = {
msg: string;
featureData: FeatureMetadata;
};

const FEATURE_BASE = '/api/metadata/v0';

export function getFeature(key: string, index?: string, source?: string) {
const queryParams = qs.stringify({ key, index, source });
return axios
.get(`${FEATURE_BASE}/feature?${queryParams}`)
.then((response: AxiosResponse<GetFeatureAPI>) => {
const { data, status } = response;
return {
feature: data.featureData,
statusCode: status,
};
})
.catch((e) => {
const { response } = e;
const statusMessage = response.data?.msg;
const statusCode = response?.status || 500;
return Promise.reject({
statusCode,
statusMessage,
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import reducer, {
getFeature,
getFeatureFailure,
getFeatureSuccess,
FeatureReducerState,
initialFeatureState,
} from 'ducks/feature/reducer';
import { featureMetadata } from '../../fixtures/metadata/feature';

describe('feature reducer', () => {
let testState: FeatureReducerState;
beforeEach(() => {
testState = {
isLoading: false,
statusCode: 200,
feature: initialFeatureState,
};
});

it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});

it('should handle getFeature.REQUEST', () => {
expect(reducer(testState, getFeature('testKey'))).toEqual({
...testState,
isLoading: true,
statusCode: null,
});
});

it('should handle GetFeature.SUCCESS', () => {
expect(
reducer(
testState,
getFeatureSuccess({
feature: featureMetadata,
statusCode: 202,
})
)
).toEqual({
isLoading: false,
statusCode: 202,
feature: featureMetadata,
});
});

it('should handle GetFeature.FAILURE', () => {
expect(
reducer(
testState,
getFeatureFailure({
statusCode: 500,
})
)
).toEqual({
isLoading: false,
statusCode: 500,
feature: initialFeatureState,
});
});
});
Loading