Unverified Commit e4c02787 authored by Tamika Tannis's avatar Tamika Tannis Committed by GitHub

Unit tests + Code cleanup for ducks directory (redux state management + API calls) (#185)

* allTags ducks: unit tests + code cleanup

* Different approach for common interfaces

* allTags ducks: cleanup; announcements & bookmarks ducks: unit tests + cleanup
parent bb8daeb2
......@@ -13,10 +13,10 @@ module.exports = {
statements: 45, // 75
},
'./js/ducks': {
branches: 0, // 75
functions: 0, // 75
lines: 0, // 75
statements: 0, // 75
branches: 35, // 75
functions: 30, // 75
lines: 35, // 75
statements: 35, // 75
},
'./js/fixtures': {
branches: 100,
......
......@@ -10,16 +10,16 @@ import { bindActionCreators } from 'redux';
import './styles.scss';
import { GlobalState } from 'ducks/rootReducer';
import { AnnouncementsGetRequest } from 'ducks/announcements/types';
import { announcementsGet } from 'ducks/announcements/reducer';
import { AnnouncementPost } from './types';
import { GetAnnouncementsRequest } from 'ducks/announcements/types';
import { getAnnouncements } from 'ducks/announcements/reducer';
import { AnnouncementPost } from 'interfaces';
export interface StateFromProps {
posts: AnnouncementPost[];
}
export interface DispatchFromProps {
announcementsGet: () => AnnouncementsGetRequest;
announcementsGet: () => GetAnnouncementsRequest;
}
export type AnnouncementPageProps = StateFromProps & DispatchFromProps;
......@@ -79,7 +79,7 @@ export const mapStateToProps = (state: GlobalState) => {
};
export const mapDispatchToProps = (dispatch: any) => {
return bindActionCreators({ announcementsGet } , dispatch);
return bindActionCreators({ announcementsGet: getAnnouncements } , dispatch);
};
export default connect<StateFromProps, DispatchFromProps>(mapStateToProps, mapDispatchToProps)(AnnouncementPage);
......@@ -8,7 +8,7 @@ import './styles.scss';
import AppConfig from 'config/config';
import LoadingSpinner from 'components/common/LoadingSpinner';
import TagInfo from 'components/Tags/TagInfo';
import { Tag } from 'components/Tags/types';
import { Tag } from 'interfaces';
import { GlobalState } from 'ducks/rootReducer';
import { getAllTags } from 'ducks/allTags/reducer';
......
import * as React from 'react';
import ResourceListItem from 'components/common/ResourceListItem';
import { Resource } from 'components/common/ResourceListItem/types';
import { Resource } from 'interfaces';
export interface SearchListProps {
results?: Resource[];
......
......@@ -2,7 +2,7 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import { Resource, ResourceType } from 'components/common/ResourceListItem/types';
import { Resource, ResourceType } from 'interfaces';
import ResourceListItem from 'components/common/ResourceListItem';
import SearchList, { SearchListProps, SearchListParams } from '../';
......
......@@ -13,7 +13,7 @@ import PopularTables from 'components/common/PopularTables';
import BookmarkList from 'components/common/Bookmark/BookmarkList'
import InfoButton from 'components/common/InfoButton';
import { ResourceType, TableResource } from 'components/common/ResourceListItem/types';
import { ResourceType, TableResource } from 'interfaces';
import TabsComponent from 'components/common/Tabs';
import { GlobalState } from 'ducks/rootReducer';
......
......@@ -4,7 +4,7 @@ import Pagination from 'react-js-pagination';
import { shallow } from 'enzyme';
import { ResourceType } from 'components/common/ResourceListItem/types';
import { ResourceType } from 'interfaces';
import { SearchPage, SearchPageProps, mapDispatchToProps, mapStateToProps } from '../';
import {
DOCUMENT_TITLE_SUFFIX,
......
import { Tag } from 'components/Tags/types';
interface PartitionData {
is_partitioned: boolean;
key?: string;
......
import * as React from 'react';
import { Link } from 'react-router-dom';
import { Tag } from '../types';
import { Tag } from 'interfaces';
import { logClick } from 'ducks/utilMethods';
import './styles.scss';
......
......@@ -12,7 +12,8 @@ import { updateTags } from 'ducks/tableMetadata/tags/reducer';
import { UpdateTagsRequest } from 'ducks/tableMetadata/types';
import TagInfo from "../TagInfo";
import { Tag, UpdateTagMethod, UpdateTagData } from '../types';
import { Tag } from 'interfaces';
import { UpdateTagMethod, UpdateTagData } from '../types';
// TODO: Use css-modules instead of 'import'
import './styles.scss';
......
export interface Tag {
tag_count: number;
tag_name: string;
tag_type?: string;
}
export enum UpdateTagMethod {
DELETE = 'DELETE',
PUT = 'PUT',
......
......@@ -4,7 +4,7 @@ import Pagination from 'react-js-pagination';
import { GlobalState } from "ducks/rootReducer";
import './styles.scss'
import { Bookmark } from "ducks/bookmark/types";
import { Bookmark } from 'interfaces';
import ResourceListItem from "components/common/ResourceListItem";
import {
ITEMS_PER_PAGE,
......
......@@ -3,7 +3,7 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import globalState from 'fixtures/globalState';
import { ResourceType } from 'components/common/ResourceListItem/types';
import { ResourceType } from 'interfaces';
import Pagination from 'react-js-pagination';
import ResourceListItem from 'components/common/ResourceListItem'
import { BookmarkList, BookmarkListProps, mapStateToProps } from "../";
......
import * as React from 'react';
import { Link } from 'react-router-dom';
import { LoggingParams, TableResource} from '../types';
import { LoggingParams } from '../types';
import { TableResource } from 'interfaces';
import BookmarkIcon from "components/common/Bookmark/BookmarkIcon";
export interface TableListItemProps {
......
......@@ -5,7 +5,7 @@ import { shallow } from 'enzyme';
import { Link } from 'react-router-dom';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import TableListItem, { TableListItemProps } from '../';
import { ResourceType } from '../../types';
import { ResourceType } from 'interfaces';
describe('TableListItem', () => {
const setup = (propOverrides?: Partial<TableListItemProps>) => {
......
......@@ -2,7 +2,9 @@ import * as React from 'react';
import * as Avatar from 'react-avatar';
import { Link } from 'react-router-dom';
import { LoggingParams, UserResource} from '../types';
import { LoggingParams } from '../types';
import { UserResource } from 'interfaces';
import Flag from 'components/common/Flag';
export interface UserListItemProps {
......
......@@ -7,7 +7,7 @@ import Flag from 'components/common/Flag';
import { Link } from 'react-router-dom';
import UserListItem, { UserListItemProps } from '../';
import { ResourceType } from '../../types';
import { ResourceType } from 'interfaces';
describe('UserListItem', () => {
const setup = (propOverrides?: Partial<UserListItemProps>) => {
......
import * as React from 'react'
import { LoggingParams, Resource, ResourceType, TableResource, UserResource } from './types';
import { Resource, ResourceType, TableResource, UserResource } from 'interfaces';
import { LoggingParams } from './types';
import TableListItem from './TableListItem';
import UserListItem from './UserListItem';
......
......@@ -5,7 +5,7 @@ import { shallow } from 'enzyme';
import TableListItem from '../TableListItem';
import UserListItem from '../UserListItem';
import ResourceListItem, { ListItemProps } from '../';
import { ResourceType } from '../types';
import { ResourceType } from 'interfaces';
describe('ResourceListItem', () => {
let props: ListItemProps;
......
export enum ResourceType {
table = "table",
user = "user",
dashboard = "dashboard",
}
export interface Resource {
type: ResourceType;
}
export interface TableResource extends Resource {
type: ResourceType.table;
cluster: string;
database: string;
description: string;
key: string;
// 'popular_tables' currently does not support 'last_updated_epoch'
last_updated_epoch?: number;
name: string;
schema_name: string;
}
/**
* This is a sample of the user data type which includes all fields.
* We will only need a subset of this for UserResource.
interface User {
active : boolean;
backupCodes: any[]; // Not sure of type
birthday : string | null;
department: string;
department_id: string;
email: string;
employment_type: string;
first_name: string;
github_username: string;
hris_active: boolean;
hris_number: string;
hris_source : string;
id: number;
last_name: string;
manager_email : string;
manager_id: number;
manager_hris_number: string;
mobile_phone : string | null;
name : string;
offboarded : boolean;
office: string;
role: string;
start_date : string;
team_name: string;
title: string;
work_phone: string;
}
*/
// Placeholder until the schema is defined.
export interface UserResource extends Resource {
type: ResourceType.user;
active : boolean;
birthday : string | null;
department: string;
email: string;
first_name: string;
github_username: string;
id: number;
last_name: string;
manager_email : string;
name : string;
offboarded : boolean;
office: string;
role: string;
start_date : string;
team_name: string;
title: string;
}
// Placeholder until the schema is defined.
export interface DashboardResource extends Resource {
type: ResourceType.dashboard;
title: string;
}
export interface LoggingParams {
source: string;
index: number;
......
import axios, { AxiosResponse } from 'axios';
import { Tag } from 'interfaces';
import { metadataAllTags, AllTagsResponseAPI } from '../v0';
describe('metadataAllTags', () => {
it('resolves with array of sorted result of response.data.tags on success', async () => {
const rawTags: Tag[] = [
{tag_count: 2, tag_name: 'test'},
{tag_count: 1, tag_name: 'atest'}
];
const expectedTags: Tag[] = [
{tag_count: 1, tag_name: 'atest'},
{tag_count: 2, tag_name: 'test'}
];
const mockResponse: AxiosResponse<AllTagsResponseAPI> = {
data: {
tags: rawTags,
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
const axiosMock = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockResponse));
expect.assertions(1);
await metadataAllTags().then(sortedTags => {
expect(sortedTags).toEqual(expectedTags);
});
});
});
import axios, { AxiosResponse, AxiosError } from 'axios';
import { AllTagsResponse } from '../types';
import axios, { AxiosResponse } from 'axios';
import { sortTagsAlphabetical } from 'ducks/utilMethods';
import { Tag } from 'interfaces';
export type AllTagsResponseAPI = {
msg: string;
tags: Tag[];
};
export function metadataAllTags() {
return axios.get('/api/metadata/v0/tags').then((response: AxiosResponse<AllTagsResponse>) => {
return axios.get('/api/metadata/v0/tags').then((response: AxiosResponse<AllTagsResponseAPI>) => {
return response.data.tags.sort(sortTagsAlphabetical);
})
.catch((error: AxiosError) => {
if (error.response) {
return error.response.data.tags.sort(sortTagsAlphabetical);
}
return [];
});
}
};
import {
GetAllTags, GetAllTagsRequest, GetAllTagsResponse,
Tag,
} from './types';
import { Tag } from 'interfaces';
export type AllTagsReducerAction = GetAllTagsRequest | GetAllTagsResponse;
import { GetAllTags, GetAllTagsRequest, GetAllTagsResponse } from './types';
/* ACTIONS */
export function getAllTags(): GetAllTagsRequest {
return { type: GetAllTags.REQUEST };
};
/* REDUCER */
export interface AllTagsReducerState {
allTags: Tag[];
isLoading: boolean;
}
export function getAllTags(): GetAllTagsRequest {
return { type: GetAllTags.ACTION };
}
};
const initialState: AllTagsReducerState = {
export const initialState: AllTagsReducerState = {
allTags: [],
isLoading: false,
};
export default function reducer(state: AllTagsReducerState = initialState, action: AllTagsReducerAction): AllTagsReducerState {
export default function reducer(state: AllTagsReducerState = initialState, action): AllTagsReducerState {
switch (action.type) {
case GetAllTags.ACTION:
case GetAllTags.REQUEST:
return { ...state, isLoading: true };
case GetAllTags.FAILURE:
return initialState;
case GetAllTags.SUCCESS:
return { ...state, allTags: action.payload, isLoading: false };
return { ...state, allTags: (<GetAllTagsResponse>action).payload.tags, isLoading: false };
default:
return state;
}
}
};
import { call, put, takeEvery } from 'redux-saga/effects';
import { SagaIterator } from 'redux-saga';
import { GetAllTags } from './types';
import { call, put, takeEvery } from 'redux-saga/effects';
import { metadataAllTags } from './api/v0';
import { GetAllTags } from './types';
export function* getAllTagsWorker(): SagaIterator {
try {
const tags = yield call(metadataAllTags);
yield put({ type: GetAllTags.SUCCESS, payload: tags });
yield put({ type: GetAllTags.SUCCESS, payload: { tags }});
} catch (e) {
yield put({ type: GetAllTags.FAILURE, payload: [] });
yield put({ type: GetAllTags.FAILURE, payload: { tags: [] }});
}
}
export function* getAllTagsWatcher(): SagaIterator {
yield takeEvery(GetAllTags.ACTION, getAllTagsWorker);
yield takeEvery(GetAllTags.REQUEST, getAllTagsWorker);
}
import { expectSaga, testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { metadataAllTags } from '../api/v0';
import reducer, { getAllTags, initialState, AllTagsReducerState } from '../reducer';
import { getAllTagsWatcher, getAllTagsWorker } from '../sagas';
import { GetAllTags } from '../types';
describe('allTags ducks', () => {
describe('actions', () => {
describe('getAllTags', () => {
it('should return action of type GetAllTagsRequest', () => {
expect(getAllTags()).toEqual({ type: GetAllTags.REQUEST });
});
})
});
describe('reducer', () => {
let testState: AllTagsReducerState;
beforeAll(() => {
testState = {
allTags: [],
isLoading: true,
};
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle GetAllTags.REQUEST', () => {
expect(reducer(testState, { type: GetAllTags.REQUEST })).toEqual({
allTags: [],
isLoading: true,
});
});
it('should handle GetAllTags.SUCCESS', () => {
const expectedTags = [{tag_count: 2, tag_name: 'test'}, {tag_count: 1, tag_name: 'test2'}];
expect(reducer(testState, { type: GetAllTags.SUCCESS, payload: { tags: expectedTags }})).toEqual({
allTags: expectedTags,
isLoading: false,
});
});
it('should return the initialState if GetAllTags.FAILURE', () => {
expect(reducer(testState, { type: GetAllTags.FAILURE, payload: { tags: [] } })).toEqual(initialState);
});
});
describe('sagas', () => {
describe('getAllTagsWatcher', () => {
it('takes GetAllTags.REQUEST with getAllTagsWorker', () => {
testSaga(getAllTagsWatcher)
.next()
.takeEveryEffect(GetAllTags.REQUEST, getAllTagsWorker);
});
});
describe('getAllTagsWorker', () => {
it('gets allTags', () => {
const mockTags = [{tag_count: 2, tag_name: 'test'}, {tag_count: 1, tag_name: 'test2'}];
return expectSaga(getAllTagsWorker)
.provide([
[matchers.call.fn(metadataAllTags), mockTags],
])
.put({
type: GetAllTags.SUCCESS,
payload: { tags: mockTags }
})
.run();
});
it('handles request error', () => {
return expectSaga(getAllTagsWorker)
.provide([
[matchers.call.fn(metadataAllTags), throwError(new Error())],
])
.put({
type: GetAllTags.FAILURE,
payload: { tags: [] }
})
.run();
});
});
});
});
import { Tag } from 'components/Tags/types';
export { Tag };
import { Tag } from 'interfaces';
/* API */
export type AllTagsResponse = {
msg: string;
tags: Tag[];
}
/* getAllTags */
export enum GetAllTags {
ACTION = 'amundsen/allTags/GET_ALL_TAGS',
SUCCESS = 'amundsen/allTags/GET_ALL_TAGS_SUCCESS',
FAILURE = 'amundsen/allTags/GET_ALL_TAGS_FAILURE',
REQUEST = 'amundsen/allTags/GET_REQUEST',
SUCCESS = 'amundsen/allTags/GET_SUCCESS',
FAILURE = 'amundsen/allTags/GET_FAILURE',
}
export interface GetAllTagsRequest {
type: GetAllTags.ACTION;
type: GetAllTags.REQUEST;
}
export interface GetAllTagsResponse {
type: GetAllTags.SUCCESS | GetAllTags.FAILURE;
payload: Tag[];
payload: {
tags: Tag[];
};
}
import axios, { AxiosResponse } from 'axios';
import { AnnouncementPost } from 'interfaces';
import { announcementsGet, AnnouncementsResponseAPI } from '../v0';
jest.mock('axios');
describe('announcementsGet', () => {
let expectedPosts: AnnouncementPost[];
let mockResponse: AxiosResponse<AnnouncementsResponseAPI>;
beforeAll(() => {
expectedPosts = [{ date: '12/31/1999', title: 'Test', html_content: '<div>Test content</div>' }];
mockResponse = {
data: {
posts: expectedPosts,
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
// @ts-ignore: TypeScript errors on Jest mock methods unless we extend AxiosStatic for tests
axios.mockResolvedValue(mockResponse);
});
it('resolves with array of posts from response.data on success', async () => {
expect.assertions(1);
await announcementsGet().then(posts => {
expect(posts).toEqual(expectedPosts);
});
});
afterAll(() => {
// @ts-ignore: TypeScript errors on Jest mock methods unless we extend AxiosStatic for tests
axios.mockClear();
})
});
import axios, { AxiosResponse, AxiosError } from 'axios';
import axios, { AxiosResponse } from 'axios';
import { AnnouncementsResponse } from '../types';
import { AnnouncementPost } from 'interfaces';
export type AnnouncementsResponseAPI = {
msg: string;
posts: AnnouncementPost[];
};
export function announcementsGet() {
return axios({
method: 'get',
url: '/api/announcements/v0/',
})
.then((response: AxiosResponse<AnnouncementsResponse>) => {
.then((response: AxiosResponse<AnnouncementsResponseAPI>) => {
return response.data.posts;
})
.catch((error: AxiosError) => {
return [];
});
}
};
import {
AnnouncementsGet, AnnouncementsGetRequest, AnnouncementsGetResponse,
AnnouncementPost,
} from './types';
import { AnnouncementPost } from 'interfaces';
export type AnnouncementsReducerAction = AnnouncementsGetRequest | AnnouncementsGetResponse;
import { GetAnnouncements, GetAnnouncementsRequest, GetAnnouncementsResponse } from './types';
/* ACTIONS */
export function getAnnouncements(): GetAnnouncementsRequest {
return { type: GetAnnouncements.REQUEST };
};
/* REDUCER */
export interface AnnouncementsReducerState {
posts: AnnouncementPost[];
}
export function announcementsGet(): AnnouncementsGetRequest {
return { type: AnnouncementsGet.ACTION };
}
};
const initialState: AnnouncementsReducerState = {
export const initialState: AnnouncementsReducerState = {
posts: [],
};
export default function reducer(state: AnnouncementsReducerState = initialState, action: AnnouncementsReducerAction): AnnouncementsReducerState {
export default function reducer(state: AnnouncementsReducerState = initialState, action): AnnouncementsReducerState {
switch (action.type) {
case AnnouncementsGet.FAILURE:
case AnnouncementsGet.SUCCESS:
return { posts: action.payload };
case GetAnnouncements.FAILURE:
return initialState;
case GetAnnouncements.SUCCESS:
return { posts: (<GetAnnouncementsResponse>action).payload.posts };
default:
return state;
}
}
};
......@@ -2,18 +2,16 @@ import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects';
import { announcementsGet } from './api/v0';
import { GetAnnouncements } from './types';
import { AnnouncementsGet } from './types';
export function* announcementsGetWorker(): SagaIterator {
export function* getAnnouncementsWorker(): SagaIterator {
try {
const announcements = yield call(announcementsGet);
yield put({ type: AnnouncementsGet.SUCCESS, payload: announcements });
} catch(error) {
yield put({ type: AnnouncementsGet.FAILURE, payload: [] });
const posts = yield call(announcementsGet);
yield put({ type: GetAnnouncements.SUCCESS, payload: { posts } });
} catch (e) {
yield put({ type: GetAnnouncements.FAILURE, payload: { posts: [] } });
}
}
export function* announcementsGetWatcher(): SagaIterator {
yield takeEvery(AnnouncementsGet.ACTION, announcementsGetWorker);
export function* getAnnouncementsWatcher(): SagaIterator {
yield takeEvery(GetAnnouncements.REQUEST, getAnnouncementsWorker);
}
import { expectSaga, testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { announcementsGet } from '../api/v0';
import reducer, { getAnnouncements, initialState, AnnouncementsReducerState } from '../reducer';
import { getAnnouncementsWatcher, getAnnouncementsWorker } from '../sagas';
import { GetAnnouncements } from '../types';
describe('announcements ducks', () => {
describe('actions', () => {
describe('getAnnouncements', () => {
it('should return action of type GetAnnouncementsRequest', () => {
expect(getAnnouncements()).toEqual({ type: GetAnnouncements.REQUEST });
});
})
});
describe('reducer', () => {
let testState: AnnouncementsReducerState;
beforeAll(() => {
testState = {
posts: [],
};
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
});
it('should handle GetAnnouncements.SUCCESS', () => {
const expectedPosts = [{ date: '12/31/1999', title: 'Test', html_content: '<div>Test content</div>' }];
expect(reducer(testState, { type: GetAnnouncements.SUCCESS, payload: { posts: expectedPosts }})).toEqual({
posts: expectedPosts,
});
});
it('should return the initialState if GetAnnouncements.FAILURE', () => {
expect(reducer(testState, { type: GetAnnouncements.FAILURE, payload: { posts: [] } })).toEqual(initialState);
});
});
describe('sagas', () => {
describe('getAnnouncementsWatcher', () => {
it('takes GetAnnouncements.REQUEST with getAnnouncementsWorker', () => {
testSaga(getAnnouncementsWatcher)
.next()
.takeEveryEffect(GetAnnouncements.REQUEST, getAnnouncementsWorker);
});
});
describe('getAnnouncementsWorker', () => {
it('gets posts', () => {
const mockPosts = [{ date: '12/31/1999', title: 'Test', html_content: '<div>Test content</div>' }];
return expectSaga(getAnnouncementsWorker)
.provide([
[matchers.call.fn(announcementsGet), mockPosts],
])
.put({
type: GetAnnouncements.SUCCESS,
payload: { posts: mockPosts }
})
.run();
});
it('handles request error', () => {
return expectSaga(getAnnouncementsWorker)
.provide([
[matchers.call.fn(announcementsGet), throwError(new Error())],
])
.put({
type: GetAnnouncements.FAILURE,
payload: { posts: [] }
})
.run();
});
});
});
});
import { AnnouncementPost } from 'components/AnnouncementPage/types';
export { AnnouncementPost }
import { AnnouncementPost } from 'interfaces';
/* API */
export type AnnouncementsResponse = {
msg: string;
posts: AnnouncementPost[];
}
/* getAnnouncements */
export enum AnnouncementsGet {
ACTION = 'amundsen/announcements/GET_ACTION',
export enum GetAnnouncements {
REQUEST = 'amundsen/announcements/GET_REQUEST',
SUCCESS = 'amundsen/announcements/GET_SUCCESS',
FAILURE = 'amundsen/announcements/GET_FAILURE',
}
export interface AnnouncementsGetRequest {
type: AnnouncementsGet.ACTION;
export interface GetAnnouncementsRequest {
type: GetAnnouncements.REQUEST;
}
export interface AnnouncementsGetResponse {
type: AnnouncementsGet.SUCCESS | AnnouncementsGet.FAILURE;
payload: AnnouncementPost[];
export interface GetAnnouncementsResponse {
type: GetAnnouncements.SUCCESS | GetAnnouncements.FAILURE;
payload: {
posts: AnnouncementPost[];
};
}
import axios, { AxiosResponse } from 'axios';
import { Bookmark } from 'interfaces';
import { addBookmark, getBookmarks, removeBookmark, API_PATH } from '../v0';
jest.mock('axios');
describe('addBookmark', () => {
let mockPutResponse;
let axiosMock;
beforeAll(() => {
mockPutResponse = {
data: {
bookmarks: [],
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'put').mockImplementation(() => Promise.resolve(mockPutResponse));
});
it('calls axios with correct parameters', async () => {
expect.assertions(1);
await addBookmark('test', 'table').then(data => {
expect(axiosMock).toHaveBeenCalledWith(`${API_PATH}/user/bookmark`, { type: 'table', key: 'test' });
});
});
it('returns response data', async () => {
expect.assertions(1);
await addBookmark('test', 'table').then(data => {
expect(data).toEqual(mockPutResponse.data);
});
});
afterAll(() => {
axiosMock.mockClear();
})
});
describe('getBookmarks', () => {
let mockGetResponse;
let axiosMock;
beforeAll(() => {
mockGetResponse = {
data: {
bookmarks: [],
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'get').mockImplementation(() => Promise.resolve(mockGetResponse));
});
it('calls axios with correct parameters if userId provided', async () => {
expect.assertions(1);
await getBookmarks('testUserId').then(data => {
expect(axiosMock).toHaveBeenCalledWith(`${API_PATH}/user/bookmark?user_id=testUserId`);
});
});
it('calls axios with correct parameters if userId not provided', async () => {
expect.assertions(1);
await getBookmarks().then(data => {
expect(axiosMock).toHaveBeenCalledWith(`${API_PATH}/user/bookmark`);
});
});
it('returns response data', async () => {
expect.assertions(1);
await getBookmarks('testUserId').then(data => {
expect(data).toEqual(mockGetResponse.data);
});
});
afterAll(() => {
axiosMock.mockClear();
});
});
describe('removeBookmark', () => {
let mockDeleteResponse;
let axiosMock;
beforeAll(() => {
mockDeleteResponse = {
data: {
resourceKey: 'test',
resourceType: 'table',
msg: 'Success'
},
status: 200,
statusText: '',
headers: {},
config: {}
};
axiosMock = jest.spyOn(axios, 'delete').mockImplementation(() => Promise.resolve(mockDeleteResponse));
});
it('calls axios with correct parameters', async () => {
expect.assertions(1);
await removeBookmark('testKey', 'table').then(data => {
expect(axiosMock).toHaveBeenCalledWith(`${API_PATH}/user/bookmark`, { data: { type: 'table', key: 'testKey' }});
});
});
it('returns response data', async () => {
expect.assertions(1);
await removeBookmark('test', 'table').then(data => {
expect(data).toEqual(mockDeleteResponse.data);
});
});
afterAll(() => {
axiosMock.mockClear();
});
});
import axios, { AxiosResponse, AxiosError } from 'axios';
import axios, { AxiosResponse } from 'axios';
export const API_PATH = '/api/metadata/v0';
const API_PATH = '/api/metadata/v0';
// TODO: Define types for the AxiosResponse data
export function addBookmark(resourceKey: string, resourceType: string) {
return axios.put(`${API_PATH}/user/bookmark`, { type: resourceType, key: resourceKey })
.then((response: AxiosResponse) => {
return response.data;
});
}
};
export function removeBookmark(resourceKey: string, resourceType: string) {
return axios.delete(`${API_PATH}/user/bookmark`, { data: { type: resourceType, key: resourceKey }})
.then((response: AxiosResponse) => {
return response.data;
});
}
};
export function getBookmarks(userId?: string) {
return axios.get(`${API_PATH}/user/bookmark` + (userId ? `?user_id=${userId}` : ''))
.then((response: AxiosResponse) => {
return response.data;
});
}
};
import { Bookmark } from 'interfaces';
import {
AddBookmark,
AddBookmarkRequest,
AddBookmarkResponse,
Bookmark,
GetBookmarks,
GetBookmarksForUser,
GetBookmarksForUserRequest,
GetBookmarksForUserResponse,
GetBookmarksRequest,
GetBookmarksResponse,
GetBookmarksForUser,
GetBookmarksForUserRequest,
RemoveBookmark,
RemoveBookmarkRequest,
RemoveBookmarkResponse,
} from "./types";
export type BookmarkReducerAction =
AddBookmarkRequest | AddBookmarkResponse |
GetBookmarksRequest | GetBookmarksResponse |
GetBookmarksForUserRequest | GetBookmarksForUserResponse |
RemoveBookmarkRequest | RemoveBookmarkResponse;
} from './types';
/* ACTIONS */
export function addBookmark(resourceKey: string, resourceType: string): AddBookmarkRequest {
return {
resourceKey,
resourceType,
type: AddBookmark.ACTION,
type: AddBookmark.REQUEST,
}
}
};
export function removeBookmark(resourceKey: string, resourceType: string): RemoveBookmarkRequest {
return {
resourceKey,
resourceType,
type: RemoveBookmark.ACTION,
type: RemoveBookmark.REQUEST,
}
}
};
export function getBookmarks(): GetBookmarksRequest {
return {
type: GetBookmarks.ACTION
type: GetBookmarks.REQUEST,
}
}
};
export function getBookmarksForUser(userId: string): GetBookmarksForUserRequest {
return {
userId,
type: GetBookmarksForUser.ACTION,
type: GetBookmarksForUser.REQUEST,
}
}
};
export interface BookmarkReducerState {
/* REDUCER */
export interface BookmarkReducerState {
myBookmarks: Bookmark[];
myBookmarksIsLoaded: boolean;
bookmarksForUser: Bookmark[];
}
const initialState: BookmarkReducerState = {
export const initialState: BookmarkReducerState = {
myBookmarks: [],
myBookmarksIsLoaded: false,
bookmarksForUser: [],
};
export default function reducer(state: BookmarkReducerState = initialState, action: BookmarkReducerAction): BookmarkReducerState {
export default function reducer(state: BookmarkReducerState = initialState, action): BookmarkReducerState {
switch(action.type) {
case RemoveBookmark.SUCCESS:
const { resourceKey } = action.payload;
const { resourceKey } = (<RemoveBookmarkResponse>action).payload;
return {
...state,
myBookmarks: state.myBookmarks.filter((bookmark) => bookmark.key !== resourceKey)
};
case AddBookmark.SUCCESS:
case GetBookmarks.SUCCESS:
return {
...state,
myBookmarks: action.payload,
myBookmarks: (<GetBookmarksResponse>action).payload.bookmarks,
myBookmarksIsLoaded: true,
};
case AddBookmark.FAILURE:
......
import { call, put, takeEvery } from 'redux-saga/effects';
import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects';
import {
AddBookmark,
AddBookmarkRequest,
GetBookmarks,
GetBookmarksRequest,
GetBookmarksForUser,
GetBookmarksForUserRequest,
GetBookmarksRequest,
RemoveBookmark,
RemoveBookmarkRequest
} from "./types";
} from './types';
import {
addBookmark,
removeBookmark,
getBookmarks,
} from "./api/v0";
} from './api/v0';
// AddBookmarks
export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator {
let response;
const { resourceKey, resourceType } = action;
......@@ -29,17 +27,16 @@ export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator {
// TODO - Consider adding the newly bookmarked resource directly to local store. This would save a round trip.
response = yield call(getBookmarks);
yield put({ type: AddBookmark.SUCCESS, payload: response.bookmarks });
yield put({ type: AddBookmark.SUCCESS, payload: { bookmarks: response.bookmarks } });
} catch(e) {
yield put({ type: AddBookmark.FAILURE, payload: response });
yield put({ type: AddBookmark.FAILURE, payload: { bookmarks: [] } });
}
}
export function* addBookmarkWatcher(): SagaIterator {
yield takeEvery(AddBookmark.ACTION , addBookmarkWorker)
yield takeEvery(AddBookmark.REQUEST , addBookmarkWorker)
}
// RemoveBookmarks
export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterator {
let response;
const { resourceKey, resourceType } = action;
......@@ -47,30 +44,28 @@ export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterat
response = yield call(removeBookmark, resourceKey, resourceType);
yield put({ type: RemoveBookmark.SUCCESS, payload: { resourceKey, resourceType }});
} catch(e) {
yield put({ type: RemoveBookmark.FAILURE, payload: response });
yield put({ type: RemoveBookmark.FAILURE, payload: { resourceKey, resourceType } });
}
}
export function* removeBookmarkWatcher(): SagaIterator {
yield takeEvery(RemoveBookmark.ACTION , removeBookmarkWorker)
yield takeEvery(RemoveBookmark.REQUEST , removeBookmarkWorker)
}
// GetBookmarks
export function* getBookmarksWorker(action: GetBookmarksRequest): SagaIterator {
let response;
try {
response = yield call(getBookmarks);
yield put({ type: GetBookmarks.SUCCESS, payload: response.bookmarks });
yield put({ type: GetBookmarks.SUCCESS, payload: { bookmarks: response.bookmarks } });
} catch(e) {
yield put({ type: GetBookmarks.FAILURE, payload: response });
yield put({ type: GetBookmarks.FAILURE, payload: { bookmarks: [] } });
}
}
export function* getBookmarkskWatcher(): SagaIterator {
yield takeEvery(GetBookmarks.ACTION, getBookmarksWorker)
export function* getBookmarksWatcher(): SagaIterator {
yield takeEvery(GetBookmarks.REQUEST, getBookmarksWorker)
}
// GetBookmarksForUser
export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): SagaIterator {
let response;
const { userId } = action;
......@@ -80,9 +75,9 @@ export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): S
yield put({ type: GetBookmarksForUser.SUCCESS, payload: { userId, bookmarks: response.bookmarks } });
} catch(e) {
yield put({ type: GetBookmarksForUser.FAILURE, payload: response });
yield put({ type: GetBookmarksForUser.FAILURE, payload: { userId, bookmarks: [] } });
}
}
export function* getBookmarksForUserWatcher(): SagaIterator {
yield takeEvery(GetBookmarksForUser.ACTION, getBookmarkForUserWorker)
yield takeEvery(GetBookmarksForUser.REQUEST, getBookmarkForUserWorker)
}
import { expectSaga, testSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { Bookmark, ResourceType } from 'interfaces';
import { addBookmark as addBkmrk, getBookmarks as getBkmrks, removeBookmark as removeBkmrk } from '../api/v0';
import reducer, { addBookmark, getBookmarks, getBookmarksForUser, removeBookmark, initialState, BookmarkReducerState } from '../reducer';
import {
addBookmarkWatcher, addBookmarkWorker,
getBookmarksWatcher, getBookmarksWorker,
getBookmarksForUserWatcher, getBookmarkForUserWorker,
removeBookmarkWatcher, removeBookmarkWorker,
} from '../sagas';
import {
AddBookmark, AddBookmarkRequest,
GetBookmarks,
GetBookmarksForUser, GetBookmarksForUserRequest,
RemoveBookmark, RemoveBookmarkRequest,
} from '../types';
describe('bookmark ducks', () => {
let testResourceKey;
let testResourceType;
let testUserId;
beforeAll(() => {
testResourceKey = 'key';
testResourceType = ResourceType.table;
testUserId = 'userId';
});
describe('actions', () => {
describe('addBookmark', () => {
it('should return action of type AddBookmarkRequest', () => {
expect(addBookmark(testResourceKey, testResourceType)).toEqual({ type: AddBookmark.REQUEST, resourceKey: testResourceKey, resourceType: testResourceType });
});
});
describe('getBookmarks', () => {
it('should return action of type GetBookmarksRequest', () => {
expect(getBookmarks()).toEqual({ type: GetBookmarks.REQUEST });
});
});
describe('getBookmarksForUser', () => {
it('should return action of type GetBookmarksForUserRequest', () => {
expect(getBookmarksForUser(testUserId)).toEqual({ type: GetBookmarksForUser.REQUEST, userId: testUserId });
});
});
describe('removeBookmark', () => {
it('should return action of type RemoveBookmarkRequest', () => {
expect(removeBookmark(testResourceKey, testResourceType)).toEqual({ type: RemoveBookmark.REQUEST, resourceKey: testResourceKey, resourceType: testResourceType });
});
});
});
describe('reducer', () => {
let testState: BookmarkReducerState;
let bookmarkList: Bookmark[];
beforeAll(() => {
bookmarkList = [
{
key: 'bookmarked_key',
type: ResourceType.table,
cluster: 'cluster',
database: 'database',
description: 'description',
name: 'name',
schema_name: 'schema_name',
},
];
testState = {
myBookmarks: bookmarkList,
myBookmarksIsLoaded: false,
bookmarksForUser: [],
};
});
it('should return the existing state if action is not handled', () => {
expect(reducer(testState, { type: 'INVALID.ACTION' })).toEqual(testState);
expect(reducer(testState, { type: AddBookmark.FAILURE })).toEqual(testState);
expect(reducer(testState, { type: GetBookmarks.FAILURE })).toEqual(testState);
expect(reducer(testState, { type: GetBookmarksForUser.FAILURE })).toEqual(testState);
expect(reducer(testState, { type: GetBookmarksForUser.SUCCESS })).toEqual(testState);
expect(reducer(testState, { type: RemoveBookmark.FAILURE })).toEqual(testState);
});
it('should handle RemoveBookmark.SUCCESS', () => {
expect(reducer(testState, { type: RemoveBookmark.SUCCESS, payload: { resourceType: ResourceType.table, resourceKey: 'bookmarked_key' }})).toEqual({
...testState,
myBookmarks: [],
});
});
it('should handle AddBookmark.SUCCESS', () => {
expect(reducer(initialState, { type: AddBookmark.SUCCESS, payload: { bookmarks: bookmarkList } })).toEqual({
...initialState,
myBookmarks: bookmarkList,
myBookmarksIsLoaded: true,
});
});
it('should handle GetBookmarks.SUCCESS', () => {
expect(reducer(initialState, { type: GetBookmarks.SUCCESS, payload: { bookmarks: bookmarkList } })).toEqual({
...initialState,
myBookmarks: bookmarkList,
myBookmarksIsLoaded: true,
});
});
});
describe('sagas', () => {
describe('addBookmarkWatcher', () => {
it('takes AddBookmark.REQUEST with addBookmarkWorker', () => {
testSaga(addBookmarkWatcher)
.next()
.takeEveryEffect(AddBookmark.REQUEST, addBookmarkWorker);
});
});
describe('addBookmarkWorker', () => {
let action: AddBookmarkRequest;
let testResourceKey;
let testResourceType;
beforeAll(() => {
testResourceKey = 'bookmarked_key';
testResourceType = ResourceType.table;
action = addBookmark(testResourceKey, testResourceType);
})
it('adds a bookmark', () => {
const bookmarks = [
{
key: testResourceKey,
type: testResourceType,
cluster: 'cluster',
database: 'database',
description: 'description',
name: 'name',
schema_name: 'schema_name',
},
];
return expectSaga(addBookmarkWorker, action)
.provide([
[matchers.call.fn(addBkmrk), {}],
[matchers.call.fn(getBkmrks), { bookmarks }],
])
.put({
type: AddBookmark.SUCCESS,
payload: { bookmarks }
})
.run();
});
it('handles request error', () => {
return expectSaga(addBookmarkWorker, action)
.provide([
[matchers.call.fn(addBkmrk), throwError(new Error())],
[matchers.call.fn(getBkmrks), throwError(new Error())],
])
.put({
type: AddBookmark.FAILURE,
payload: { bookmarks: [] }
})
.run();
});
});
describe('getBookmarksWatcher', () => {
it('takes GetBookmark.REQUEST with getBookmarksWorker', () => {
testSaga(getBookmarksWatcher)
.next()
.takeEveryEffect(GetBookmarks.REQUEST, getBookmarksWorker);
});
});
describe('getBookmarksWorker', () => {
it('gets bookmarks', () => {
const bookmarks = [
{
key: testResourceKey,
type: testResourceType,
cluster: 'cluster',
database: 'database',
description: 'description',
name: 'name',
schema_name: 'schema_name',
},
];
return expectSaga(getBookmarksWorker)
.provide([
[matchers.call.fn(getBkmrks), { bookmarks }],
])
.put({
type: GetBookmarks.SUCCESS,
payload: { bookmarks }
})
.run();
});
it('handles request error', () => {
return expectSaga(getBookmarksWorker)
.provide([
[matchers.call.fn(getBkmrks), throwError(new Error())],
])
.put({
type: GetBookmarks.FAILURE,
payload: { bookmarks: [] }
})
.run();
});
});
describe('getBookmarksForUserWatcher', () => {
it('takes GetBookmarksForUser.REQUEST with getBookmarkForUserWorker', () => {
testSaga(getBookmarksForUserWatcher)
.next()
.takeEveryEffect(GetBookmarksForUser.REQUEST, getBookmarkForUserWorker);
});
});
describe('getBookmarkForUserWorker', () => {
let action: GetBookmarksForUserRequest;
let testUserId;
beforeAll(() => {
testUserId = 'userId';
action = getBookmarksForUser(testUserId);
});
it('adds a bookmark', () => {
const bookmarks = [
{
key: testResourceKey,
type: testResourceType,
cluster: 'cluster',
database: 'database',
description: 'description',
name: 'name',
schema_name: 'schema_name',
},
];
return expectSaga(getBookmarkForUserWorker, action)
.provide([
[matchers.call.fn(getBkmrks), { bookmarks, userId: action.userId }],
])
.put({
type: GetBookmarksForUser.SUCCESS,
payload: { bookmarks, userId: action.userId }
})
.run();
});
it('handles request error', () => {
return expectSaga(getBookmarkForUserWorker, action)
.provide([
[matchers.call.fn(getBkmrks), throwError(new Error())],
])
.put({
type: GetBookmarksForUser.FAILURE,
payload: { bookmarks: [], userId: action.userId }
})
.run();
});
});
describe('removeBookmarkWatcher', () => {
it('takes RemoveBookmark.REQUEST with removeBookmarkWorker', () => {
testSaga(removeBookmarkWatcher)
.next()
.takeEveryEffect(RemoveBookmark.REQUEST, removeBookmarkWorker);
});
});
describe('removeBookmarkWorker', () => {
let action: RemoveBookmarkRequest;
let testResourceKey;
let testResourceType;
beforeAll(() => {
testResourceKey = 'bookmarked_key';
testResourceType = ResourceType.table;
action = removeBookmark(testResourceKey, testResourceType);
})
it('removes a bookmark', () => {
return expectSaga(removeBookmarkWorker, action)
.provide([
[matchers.call.fn(removeBkmrk), {}],
])
.put({
type: RemoveBookmark.SUCCESS,
payload: { resourceKey: action.resourceKey, resourceType: action.resourceType }
})
.run();
});
it('handles request error', () => {
return expectSaga(removeBookmarkWorker, action)
.provide([
[matchers.call.fn(removeBkmrk), throwError(new Error())],
])
.put({
type: RemoveBookmark.FAILURE,
payload: { resourceKey: action.resourceKey, resourceType: action.resourceType }
})
.run();
});
});
});
});
import { TableResource } from "components/common/ResourceListItem/types";
import { Bookmark } from 'interfaces';
export type Bookmark = TableResource;
// AddBookmark
export enum AddBookmark {
ACTION = 'amundsen/bookmark/ADD',
REQUEST = 'amundsen/bookmark/ADD_REQUEST',
SUCCESS = 'amundsen/bookmark/ADD_SUCCESS',
FAILURE = 'amundsen/bookmark/ADD_FAILURE',
}
export interface AddBookmarkRequest {
type: AddBookmark.ACTION;
type: AddBookmark.REQUEST;
resourceKey: string;
resourceType: string;
}
export interface AddBookmarkResponse {
type: AddBookmark.SUCCESS | AddBookmark.FAILURE;
payload: Bookmark[];
payload: {
bookmarks: Bookmark[];
}
}
// RemoveBookmark
export enum RemoveBookmark {
ACTION = 'amundsen/bookmark/REMOVE',
REQUEST = 'amundsen/bookmark/REMOVE_REQUEST',
SUCCESS = 'amundsen/bookmark/REMOVE_SUCCESS',
FAILURE = 'amundsen/bookmark/REMOVE_FAILURE',
}
export interface RemoveBookmarkRequest {
type: RemoveBookmark.ACTION;
type: RemoveBookmark.REQUEST;
resourceKey: string;
resourceType: string;
}
......@@ -38,30 +35,30 @@ export interface RemoveBookmarkResponse {
};
}
// GetBookmarks - Get all bookmarks for the logged in user. This result will be cached
// GetBookmarks - Get all bookmarks for the logged in user. This result will be cached
export enum GetBookmarks {
ACTION = 'amundsen/bookmark/GET',
REQUEST = 'amundsen/bookmark/GET_REQUEST',
SUCCESS = 'amundsen/bookmark/GET_SUCCESS',
FAILURE = 'amundsen/bookmark/GET_FAILURE',
}
export interface GetBookmarksRequest {
type: GetBookmarks.ACTION;
type: GetBookmarks.REQUEST;
}
export interface GetBookmarksResponse {
type: GetBookmarks.SUCCESS | GetBookmarks.FAILURE;
payload: Bookmark[];
payload: {
bookmarks: Bookmark[];
};
}
// GetBookmarksForUser - Get all bookmarks for a specified user
// GetBookmarksForUser - Get all bookmarks for a specified user
export enum GetBookmarksForUser {
ACTION = 'amundsen/bookmark/GET_FOR_USER',
REQUEST = 'amundsen/bookmark/GET_FOR_USER_REQUEST',
SUCCESS = 'amundsen/bookmark/GET_FOR_USER_SUCCESS',
FAILURE = 'amundsen/bookmark/GET_FOR_USER_FAILURE',
}
export interface GetBookmarksForUserRequest {
type: GetBookmarksForUser.ACTION;
type: GetBookmarksForUser.REQUEST;
userId: string;
}
export interface GetBookmarksForUserResponse {
......
import { TableResource } from 'components/common/ResourceListItem/types';
import { TableResource } from 'interfaces';
export { TableResource };
/* API */
......
import { all } from 'redux-saga/effects';
// AnnouncementPage
import { announcementsGetWatcher } from "./announcements/sagas";
import { getAnnouncementsWatcher } from "./announcements/sagas";
import {
addBookmarkWatcher,
getBookmarksForUserWatcher,
getBookmarkskWatcher,
getBookmarksWatcher,
removeBookmarkWatcher
} from "ducks/bookmark/sagas";
......@@ -39,11 +39,11 @@ import { getLoggedInUserWatcher, getUserWatcher } from "./user/sagas";
export default function* rootSaga() {
yield all([
// AnnouncementPage
announcementsGetWatcher(),
getAnnouncementsWatcher(),
// Bookmarks
addBookmarkWatcher(),
getBookmarksForUserWatcher(),
getBookmarkskWatcher(),
getBookmarksWatcher(),
removeBookmarkWatcher(),
// FeedbackForm
submitFeedbackWatcher(),
......
......@@ -10,7 +10,7 @@ import {
TableSearchResults,
UserSearchResults,
} from './types';
import { ResourceType } from 'components/common/ResourceListItem/types';
import { ResourceType } from 'interfaces';
export type SearchReducerAction = SearchAllResponse | SearchResourceResponse | SearchAllRequest | SearchResourceRequest;
......
......@@ -4,7 +4,7 @@ import {
DashboardResource,
TableResource,
UserResource,
} from 'components/common/ResourceListItem/types';
} from 'interfaces';
import { SearchReducerState } from './reducer';
interface SearchResults<T extends Resource> {
......
import { PreviewData, PreviewQueryParams, TableMetadata, User } from 'components/TableDetail/types';
import { UpdateTagData, Tag } from 'components/Tags/types';
import { UpdateTagData } from 'components/Tags/types';
import { UpdateMethod } from 'components/TableDetail/OwnerEditor/types';
export { PreviewData, PreviewQueryParams, TableMetadata, Tag, User, UpdateMethod, UpdateTagData };
import { Tag } from 'interfaces';
export { PreviewData, PreviewQueryParams, TableMetadata, User, Tag, UpdateMethod, UpdateTagData };
type MessageResponse = { msg: string };
type TableData = TableMetadata & {
......
import { ActionLogParams, postActionLog } from "./log/api/v0";
import { Tag } from 'components/Tags/types';
import { Tag } from 'interfaces';
export function sortTagsAlphabetical(a: Tag, b: Tag): number {
return a.tag_name.localeCompare(b.tag_name);
......
import { GlobalState } from 'ducks/rootReducer';
import { SendingState } from 'components/Feedback/types';
import { ResourceType } from 'components/common/ResourceListItem/types';
import { ResourceType } from 'interfaces';
const globalState: GlobalState = {
announcements: {
......
export enum ResourceType {
table = "table",
user = "user",
dashboard = "dashboard",
};
export interface Resource {
type: ResourceType;
};
// Placeholder until the schema is defined.
export interface DashboardResource extends Resource {
type: ResourceType.dashboard;
title: string;
}
export interface TableResource extends Resource {
type: ResourceType.table;
cluster: string;
database: string;
description: string;
key: string;
// 'popular_tables' currently does not support 'last_updated_epoch'
last_updated_epoch?: number;
name: string;
schema_name: string;
};
/**
* This is a sample of the user data type which includes all fields.
* We will only need a subset of this for UserResource.
interface User {
active : boolean;
backupCodes: any[]; // Not sure of type
birthday : string | null;
department: string;
department_id: string;
email: string;
employment_type: string;
first_name: string;
github_username: string;
hris_active: boolean;
hris_number: string;
hris_source : string;
id: number;
last_name: string;
manager_email : string;
manager_id: number;
manager_hris_number: string;
mobile_phone : string | null;
name : string;
offboarded : boolean;
office: string;
role: string;
start_date : string;
team_name: string;
title: string;
work_phone: string;
}
*/
// Placeholder until the schema is defined.
export interface UserResource extends Resource {
type: ResourceType.user;
active : boolean;
birthday : string | null;
department: string;
email: string;
first_name: string;
github_username: string;
id: number;
last_name: string;
manager_email : string;
name : string;
offboarded : boolean;
office: string;
role: string;
start_date : string;
team_name: string;
title: string;
}
export type Bookmark = TableResource & {};
export interface Tag {
tag_count: number;
tag_name: string;
tag_type?: string;
}
export * from './Announcements';
export * from './Resources';
export * from './Tags';
......@@ -716,6 +716,18 @@
"es-abstract": "^1.7.0"
}
},
"array-map": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
"integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
"dev": true
},
"array-reduce": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
"integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
"dev": true
},
"array-union": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
......@@ -5444,6 +5456,12 @@
}
}
},
"fsm-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fsm-iterator/-/fsm-iterator-1.1.0.tgz",
"integrity": "sha1-M33kXeGesgV4jPAuOpVewgZ2Dew=",
"dev": true
},
"fstream": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
......@@ -8627,6 +8645,12 @@
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true
},
"json3": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.0.tgz",
"integrity": "sha1-Dp5/bF0nC3WJKa9Nb+/chL1m4lk=",
"dev": true
},
"json5": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
......@@ -8828,6 +8852,12 @@
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true
},
"lodash.ismatch": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
"integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=",
"dev": true
},
"lodash.isplainobject": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz",
......@@ -12113,6 +12143,20 @@
"integrity": "sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w==",
"dev": true
},
"redux-saga-test-plan": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/redux-saga-test-plan/-/redux-saga-test-plan-3.7.0.tgz",
"integrity": "sha512-et9kCnME01kjoKXFfSk4FkozgOPPvllt9TlpL6A7ZYIS/WgoEFMLXk/UYww8KWXbmk5Qo2IF6xCc/IS1KmvP6A==",
"dev": true,
"requires": {
"core-js": "^2.4.1",
"fsm-iterator": "^1.1.0",
"lodash.isequal": "^4.5.0",
"lodash.ismatch": "^4.4.0",
"object-assign": "^4.1.0",
"util-inspect": "^0.1.8"
}
},
"reflect-metadata": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz",
......@@ -14917,6 +14961,41 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"util-inspect": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/util-inspect/-/util-inspect-0.1.8.tgz",
"integrity": "sha1-KznbzS2SHy2EMJI8r/QPS1zqXbE=",
"dev": true,
"requires": {
"array-map": "0.0.0",
"array-reduce": "0.0.0",
"foreach": "2.0.4",
"indexof": "0.0.1",
"isarray": "0.0.1",
"json3": "3.3.0",
"object-keys": "0.5.0"
},
"dependencies": {
"foreach": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.4.tgz",
"integrity": "sha1-zF0NiuHUbMmlVcJoL5EJd4WZNd8=",
"dev": true
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
},
"object-keys": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.5.0.tgz",
"integrity": "sha1-CeIR8+ADGK/E9ZLjbnzcENmtcpM=",
"dev": true
}
}
},
"util.promisify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
......
......@@ -58,6 +58,7 @@
"prettier": "^1.12.1",
"redux-mock-store": "^1.5.3",
"redux-saga": "^0.16.2",
"redux-saga-test-plan": "^3.7.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.20.3",
"terser-webpack-plugin": "^1.1.0",
......
......@@ -20,6 +20,7 @@
"components/*": [ "components/*" ],
"config/*": [ "config/*" ],
"ducks/*": [ "ducks/*" ],
"interfaces/*": [ "interfaces/*" ],
}
},
"exclude": [
......
......@@ -19,6 +19,7 @@ const config: webpack.Configuration = {
components: path.join(__dirname, '/js/components'),
config: path.join(__dirname, '/js/config'),
ducks: path.join(__dirname, '/js/ducks'),
interfaces: path.join(__dirname, '/js/interfaces'),
},
extensions: ['.tsx', '.ts', '.js', '.jsx', '.css', '.scss'],
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment