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 = { ...@@ -13,10 +13,10 @@ module.exports = {
statements: 45, // 75 statements: 45, // 75
}, },
'./js/ducks': { './js/ducks': {
branches: 0, // 75 branches: 35, // 75
functions: 0, // 75 functions: 30, // 75
lines: 0, // 75 lines: 35, // 75
statements: 0, // 75 statements: 35, // 75
}, },
'./js/fixtures': { './js/fixtures': {
branches: 100, branches: 100,
......
...@@ -10,16 +10,16 @@ import { bindActionCreators } from 'redux'; ...@@ -10,16 +10,16 @@ import { bindActionCreators } from 'redux';
import './styles.scss'; import './styles.scss';
import { GlobalState } from 'ducks/rootReducer'; import { GlobalState } from 'ducks/rootReducer';
import { AnnouncementsGetRequest } from 'ducks/announcements/types'; import { GetAnnouncementsRequest } from 'ducks/announcements/types';
import { announcementsGet } from 'ducks/announcements/reducer'; import { getAnnouncements } from 'ducks/announcements/reducer';
import { AnnouncementPost } from './types'; import { AnnouncementPost } from 'interfaces';
export interface StateFromProps { export interface StateFromProps {
posts: AnnouncementPost[]; posts: AnnouncementPost[];
} }
export interface DispatchFromProps { export interface DispatchFromProps {
announcementsGet: () => AnnouncementsGetRequest; announcementsGet: () => GetAnnouncementsRequest;
} }
export type AnnouncementPageProps = StateFromProps & DispatchFromProps; export type AnnouncementPageProps = StateFromProps & DispatchFromProps;
...@@ -79,7 +79,7 @@ export const mapStateToProps = (state: GlobalState) => { ...@@ -79,7 +79,7 @@ export const mapStateToProps = (state: GlobalState) => {
}; };
export const mapDispatchToProps = (dispatch: any) => { export const mapDispatchToProps = (dispatch: any) => {
return bindActionCreators({ announcementsGet } , dispatch); return bindActionCreators({ announcementsGet: getAnnouncements } , dispatch);
}; };
export default connect<StateFromProps, DispatchFromProps>(mapStateToProps, mapDispatchToProps)(AnnouncementPage); export default connect<StateFromProps, DispatchFromProps>(mapStateToProps, mapDispatchToProps)(AnnouncementPage);
...@@ -8,7 +8,7 @@ import './styles.scss'; ...@@ -8,7 +8,7 @@ import './styles.scss';
import AppConfig from 'config/config'; import AppConfig from 'config/config';
import LoadingSpinner from 'components/common/LoadingSpinner'; import LoadingSpinner from 'components/common/LoadingSpinner';
import TagInfo from 'components/Tags/TagInfo'; import TagInfo from 'components/Tags/TagInfo';
import { Tag } from 'components/Tags/types'; import { Tag } from 'interfaces';
import { GlobalState } from 'ducks/rootReducer'; import { GlobalState } from 'ducks/rootReducer';
import { getAllTags } from 'ducks/allTags/reducer'; import { getAllTags } from 'ducks/allTags/reducer';
......
import * as React from 'react'; import * as React from 'react';
import ResourceListItem from 'components/common/ResourceListItem'; import ResourceListItem from 'components/common/ResourceListItem';
import { Resource } from 'components/common/ResourceListItem/types'; import { Resource } from 'interfaces';
export interface SearchListProps { export interface SearchListProps {
results?: Resource[]; results?: Resource[];
......
...@@ -2,7 +2,7 @@ import * as React from 'react'; ...@@ -2,7 +2,7 @@ import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Resource, ResourceType } from 'components/common/ResourceListItem/types'; import { Resource, ResourceType } from 'interfaces';
import ResourceListItem from 'components/common/ResourceListItem'; import ResourceListItem from 'components/common/ResourceListItem';
import SearchList, { SearchListProps, SearchListParams } from '../'; import SearchList, { SearchListProps, SearchListParams } from '../';
......
...@@ -13,7 +13,7 @@ import PopularTables from 'components/common/PopularTables'; ...@@ -13,7 +13,7 @@ import PopularTables from 'components/common/PopularTables';
import BookmarkList from 'components/common/Bookmark/BookmarkList' import BookmarkList from 'components/common/Bookmark/BookmarkList'
import InfoButton from 'components/common/InfoButton'; 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 TabsComponent from 'components/common/Tabs';
import { GlobalState } from 'ducks/rootReducer'; import { GlobalState } from 'ducks/rootReducer';
......
...@@ -4,7 +4,7 @@ import Pagination from 'react-js-pagination'; ...@@ -4,7 +4,7 @@ import Pagination from 'react-js-pagination';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { ResourceType } from 'components/common/ResourceListItem/types'; import { ResourceType } from 'interfaces';
import { SearchPage, SearchPageProps, mapDispatchToProps, mapStateToProps } from '../'; import { SearchPage, SearchPageProps, mapDispatchToProps, mapStateToProps } from '../';
import { import {
DOCUMENT_TITLE_SUFFIX, DOCUMENT_TITLE_SUFFIX,
......
import { Tag } from 'components/Tags/types';
interface PartitionData { interface PartitionData {
is_partitioned: boolean; is_partitioned: boolean;
key?: string; key?: string;
......
import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Tag } from '../types'; import { Tag } from 'interfaces';
import { logClick } from 'ducks/utilMethods'; import { logClick } from 'ducks/utilMethods';
import './styles.scss'; import './styles.scss';
......
...@@ -12,7 +12,8 @@ import { updateTags } from 'ducks/tableMetadata/tags/reducer'; ...@@ -12,7 +12,8 @@ import { updateTags } from 'ducks/tableMetadata/tags/reducer';
import { UpdateTagsRequest } from 'ducks/tableMetadata/types'; import { UpdateTagsRequest } from 'ducks/tableMetadata/types';
import TagInfo from "../TagInfo"; 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' // TODO: Use css-modules instead of 'import'
import './styles.scss'; import './styles.scss';
......
export interface Tag {
tag_count: number;
tag_name: string;
tag_type?: string;
}
export enum UpdateTagMethod { export enum UpdateTagMethod {
DELETE = 'DELETE', DELETE = 'DELETE',
PUT = 'PUT', PUT = 'PUT',
......
...@@ -4,7 +4,7 @@ import Pagination from 'react-js-pagination'; ...@@ -4,7 +4,7 @@ import Pagination from 'react-js-pagination';
import { GlobalState } from "ducks/rootReducer"; import { GlobalState } from "ducks/rootReducer";
import './styles.scss' import './styles.scss'
import { Bookmark } from "ducks/bookmark/types"; import { Bookmark } from 'interfaces';
import ResourceListItem from "components/common/ResourceListItem"; import ResourceListItem from "components/common/ResourceListItem";
import { import {
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
......
...@@ -3,7 +3,7 @@ import * as React from 'react'; ...@@ -3,7 +3,7 @@ import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import globalState from 'fixtures/globalState'; import globalState from 'fixtures/globalState';
import { ResourceType } from 'components/common/ResourceListItem/types'; import { ResourceType } from 'interfaces';
import Pagination from 'react-js-pagination'; import Pagination from 'react-js-pagination';
import ResourceListItem from 'components/common/ResourceListItem' import ResourceListItem from 'components/common/ResourceListItem'
import { BookmarkList, BookmarkListProps, mapStateToProps } from "../"; import { BookmarkList, BookmarkListProps, mapStateToProps } from "../";
......
import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router-dom'; 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"; import BookmarkIcon from "components/common/Bookmark/BookmarkIcon";
export interface TableListItemProps { export interface TableListItemProps {
......
...@@ -5,7 +5,7 @@ import { shallow } from 'enzyme'; ...@@ -5,7 +5,7 @@ import { shallow } from 'enzyme';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon'; import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import TableListItem, { TableListItemProps } from '../'; import TableListItem, { TableListItemProps } from '../';
import { ResourceType } from '../../types'; import { ResourceType } from 'interfaces';
describe('TableListItem', () => { describe('TableListItem', () => {
const setup = (propOverrides?: Partial<TableListItemProps>) => { const setup = (propOverrides?: Partial<TableListItemProps>) => {
......
...@@ -2,7 +2,9 @@ import * as React from 'react'; ...@@ -2,7 +2,9 @@ import * as React from 'react';
import * as Avatar from 'react-avatar'; import * as Avatar from 'react-avatar';
import { Link } from 'react-router-dom'; 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'; import Flag from 'components/common/Flag';
export interface UserListItemProps { export interface UserListItemProps {
......
...@@ -7,7 +7,7 @@ import Flag from 'components/common/Flag'; ...@@ -7,7 +7,7 @@ import Flag from 'components/common/Flag';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import UserListItem, { UserListItemProps } from '../'; import UserListItem, { UserListItemProps } from '../';
import { ResourceType } from '../../types'; import { ResourceType } from 'interfaces';
describe('UserListItem', () => { describe('UserListItem', () => {
const setup = (propOverrides?: Partial<UserListItemProps>) => { const setup = (propOverrides?: Partial<UserListItemProps>) => {
......
import * as React from 'react' 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 TableListItem from './TableListItem';
import UserListItem from './UserListItem'; import UserListItem from './UserListItem';
......
...@@ -5,7 +5,7 @@ import { shallow } from 'enzyme'; ...@@ -5,7 +5,7 @@ import { shallow } from 'enzyme';
import TableListItem from '../TableListItem'; import TableListItem from '../TableListItem';
import UserListItem from '../UserListItem'; import UserListItem from '../UserListItem';
import ResourceListItem, { ListItemProps } from '../'; import ResourceListItem, { ListItemProps } from '../';
import { ResourceType } from '../types'; import { ResourceType } from 'interfaces';
describe('ResourceListItem', () => { describe('ResourceListItem', () => {
let props: ListItemProps; 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 { export interface LoggingParams {
source: string; source: string;
index: number; 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 axios, { AxiosResponse } from 'axios';
import { AllTagsResponse } from '../types';
import { sortTagsAlphabetical } from 'ducks/utilMethods'; import { sortTagsAlphabetical } from 'ducks/utilMethods';
import { Tag } from 'interfaces';
export type AllTagsResponseAPI = {
msg: string;
tags: Tag[];
};
export function metadataAllTags() { 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); return response.data.tags.sort(sortTagsAlphabetical);
}) })
.catch((error: AxiosError) => { };
if (error.response) {
return error.response.data.tags.sort(sortTagsAlphabetical);
}
return [];
});
}
import { import { Tag } from 'interfaces';
GetAllTags, GetAllTagsRequest, GetAllTagsResponse,
Tag,
} from './types';
export type AllTagsReducerAction = GetAllTagsRequest | GetAllTagsResponse; import { GetAllTags, GetAllTagsRequest, GetAllTagsResponse } from './types';
/* ACTIONS */
export function getAllTags(): GetAllTagsRequest {
return { type: GetAllTags.REQUEST };
};
/* REDUCER */
export interface AllTagsReducerState { export interface AllTagsReducerState {
allTags: Tag[]; allTags: Tag[];
isLoading: boolean; isLoading: boolean;
} };
export function getAllTags(): GetAllTagsRequest {
return { type: GetAllTags.ACTION };
}
const initialState: AllTagsReducerState = { export const initialState: AllTagsReducerState = {
allTags: [], allTags: [],
isLoading: false, isLoading: false,
}; };
export default function reducer(state: AllTagsReducerState = initialState, action: AllTagsReducerAction): AllTagsReducerState { export default function reducer(state: AllTagsReducerState = initialState, action): AllTagsReducerState {
switch (action.type) { switch (action.type) {
case GetAllTags.ACTION: case GetAllTags.REQUEST:
return { ...state, isLoading: true }; return { ...state, isLoading: true };
case GetAllTags.FAILURE: case GetAllTags.FAILURE:
return initialState;
case GetAllTags.SUCCESS: case GetAllTags.SUCCESS:
return { ...state, allTags: action.payload, isLoading: false }; return { ...state, allTags: (<GetAllTagsResponse>action).payload.tags, isLoading: false };
default: default:
return state; return state;
} }
} };
import { call, put, takeEvery } from 'redux-saga/effects';
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects';
import { GetAllTags } from './types';
import { metadataAllTags } from './api/v0'; import { metadataAllTags } from './api/v0';
import { GetAllTags } from './types';
export function* getAllTagsWorker(): SagaIterator { export function* getAllTagsWorker(): SagaIterator {
try { try {
const tags = yield call(metadataAllTags); const tags = yield call(metadataAllTags);
yield put({ type: GetAllTags.SUCCESS, payload: tags }); yield put({ type: GetAllTags.SUCCESS, payload: { tags }});
} catch (e) { } catch (e) {
yield put({ type: GetAllTags.FAILURE, payload: [] }); yield put({ type: GetAllTags.FAILURE, payload: { tags: [] }});
} }
} }
export function* getAllTagsWatcher(): SagaIterator { 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'; import { Tag } from 'interfaces';
export { Tag };
/* API */
export type AllTagsResponse = {
msg: string;
tags: Tag[];
}
/* getAllTags */
export enum GetAllTags { export enum GetAllTags {
ACTION = 'amundsen/allTags/GET_ALL_TAGS', REQUEST = 'amundsen/allTags/GET_REQUEST',
SUCCESS = 'amundsen/allTags/GET_ALL_TAGS_SUCCESS', SUCCESS = 'amundsen/allTags/GET_SUCCESS',
FAILURE = 'amundsen/allTags/GET_ALL_TAGS_FAILURE', FAILURE = 'amundsen/allTags/GET_FAILURE',
} }
export interface GetAllTagsRequest { export interface GetAllTagsRequest {
type: GetAllTags.ACTION; type: GetAllTags.REQUEST;
} }
export interface GetAllTagsResponse { export interface GetAllTagsResponse {
type: GetAllTags.SUCCESS | GetAllTags.FAILURE; 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() { export function announcementsGet() {
return axios({ return axios({
method: 'get', method: 'get',
url: '/api/announcements/v0/', url: '/api/announcements/v0/',
}) })
.then((response: AxiosResponse<AnnouncementsResponse>) => { .then((response: AxiosResponse<AnnouncementsResponseAPI>) => {
return response.data.posts; return response.data.posts;
}) })
.catch((error: AxiosError) => { };
return [];
});
}
import { import { AnnouncementPost } from 'interfaces';
AnnouncementsGet, AnnouncementsGetRequest, AnnouncementsGetResponse,
AnnouncementPost,
} from './types';
export type AnnouncementsReducerAction = AnnouncementsGetRequest | AnnouncementsGetResponse; import { GetAnnouncements, GetAnnouncementsRequest, GetAnnouncementsResponse } from './types';
/* ACTIONS */
export function getAnnouncements(): GetAnnouncementsRequest {
return { type: GetAnnouncements.REQUEST };
};
/* REDUCER */
export interface AnnouncementsReducerState { export interface AnnouncementsReducerState {
posts: AnnouncementPost[]; posts: AnnouncementPost[];
} };
export function announcementsGet(): AnnouncementsGetRequest {
return { type: AnnouncementsGet.ACTION };
}
const initialState: AnnouncementsReducerState = { export const initialState: AnnouncementsReducerState = {
posts: [], posts: [],
}; };
export default function reducer(state: AnnouncementsReducerState = initialState, action: AnnouncementsReducerAction): AnnouncementsReducerState { export default function reducer(state: AnnouncementsReducerState = initialState, action): AnnouncementsReducerState {
switch (action.type) { switch (action.type) {
case AnnouncementsGet.FAILURE: case GetAnnouncements.FAILURE:
case AnnouncementsGet.SUCCESS: return initialState;
return { posts: action.payload }; case GetAnnouncements.SUCCESS:
return { posts: (<GetAnnouncementsResponse>action).payload.posts };
default: default:
return state; return state;
} }
} };
...@@ -2,18 +2,16 @@ import { SagaIterator } from 'redux-saga'; ...@@ -2,18 +2,16 @@ import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { announcementsGet } from './api/v0'; import { announcementsGet } from './api/v0';
import { GetAnnouncements } from './types';
import { AnnouncementsGet } from './types'; export function* getAnnouncementsWorker(): SagaIterator {
export function* announcementsGetWorker(): SagaIterator {
try { try {
const announcements = yield call(announcementsGet); const posts = yield call(announcementsGet);
yield put({ type: AnnouncementsGet.SUCCESS, payload: announcements }); yield put({ type: GetAnnouncements.SUCCESS, payload: { posts } });
} catch(error) { } catch (e) {
yield put({ type: AnnouncementsGet.FAILURE, payload: [] }); yield put({ type: GetAnnouncements.FAILURE, payload: { posts: [] } });
} }
} }
export function* getAnnouncementsWatcher(): SagaIterator {
export function* announcementsGetWatcher(): SagaIterator { yield takeEvery(GetAnnouncements.REQUEST, getAnnouncementsWorker);
yield takeEvery(AnnouncementsGet.ACTION, announcementsGetWorker);
} }
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'; import { AnnouncementPost } from 'interfaces';
export { AnnouncementPost }
/* API */ export enum GetAnnouncements {
export type AnnouncementsResponse = { REQUEST = 'amundsen/announcements/GET_REQUEST',
msg: string;
posts: AnnouncementPost[];
}
/* getAnnouncements */
export enum AnnouncementsGet {
ACTION = 'amundsen/announcements/GET_ACTION',
SUCCESS = 'amundsen/announcements/GET_SUCCESS', SUCCESS = 'amundsen/announcements/GET_SUCCESS',
FAILURE = 'amundsen/announcements/GET_FAILURE', FAILURE = 'amundsen/announcements/GET_FAILURE',
} }
export interface AnnouncementsGetRequest { export interface GetAnnouncementsRequest {
type: AnnouncementsGet.ACTION; type: GetAnnouncements.REQUEST;
} }
export interface AnnouncementsGetResponse { export interface GetAnnouncementsResponse {
type: AnnouncementsGet.SUCCESS | AnnouncementsGet.FAILURE; type: GetAnnouncements.SUCCESS | GetAnnouncements.FAILURE;
payload: AnnouncementPost[]; 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) { export function addBookmark(resourceKey: string, resourceType: string) {
return axios.put(`${API_PATH}/user/bookmark`, { type: resourceType, key: resourceKey }) return axios.put(`${API_PATH}/user/bookmark`, { type: resourceType, key: resourceKey })
.then((response: AxiosResponse) => { .then((response: AxiosResponse) => {
return response.data; return response.data;
}); });
} };
export function removeBookmark(resourceKey: string, resourceType: string) { export function removeBookmark(resourceKey: string, resourceType: string) {
return axios.delete(`${API_PATH}/user/bookmark`, { data: { type: resourceType, key: resourceKey }}) return axios.delete(`${API_PATH}/user/bookmark`, { data: { type: resourceType, key: resourceKey }})
.then((response: AxiosResponse) => { .then((response: AxiosResponse) => {
return response.data; return response.data;
}); });
} };
export function getBookmarks(userId?: string) { export function getBookmarks(userId?: string) {
return axios.get(`${API_PATH}/user/bookmark` + (userId ? `?user_id=${userId}` : '')) return axios.get(`${API_PATH}/user/bookmark` + (userId ? `?user_id=${userId}` : ''))
.then((response: AxiosResponse) => { .then((response: AxiosResponse) => {
return response.data; return response.data;
}); });
} };
import { Bookmark } from 'interfaces';
import { import {
AddBookmark, AddBookmark,
AddBookmarkRequest, AddBookmarkRequest,
AddBookmarkResponse,
Bookmark,
GetBookmarks, GetBookmarks,
GetBookmarksForUser,
GetBookmarksForUserRequest,
GetBookmarksForUserResponse,
GetBookmarksRequest, GetBookmarksRequest,
GetBookmarksResponse, GetBookmarksResponse,
GetBookmarksForUser,
GetBookmarksForUserRequest,
RemoveBookmark, RemoveBookmark,
RemoveBookmarkRequest, RemoveBookmarkRequest,
RemoveBookmarkResponse, RemoveBookmarkResponse,
} from "./types"; } from './types';
export type BookmarkReducerAction =
AddBookmarkRequest | AddBookmarkResponse |
GetBookmarksRequest | GetBookmarksResponse |
GetBookmarksForUserRequest | GetBookmarksForUserResponse |
RemoveBookmarkRequest | RemoveBookmarkResponse;
/* ACTIONS */
export function addBookmark(resourceKey: string, resourceType: string): AddBookmarkRequest { export function addBookmark(resourceKey: string, resourceType: string): AddBookmarkRequest {
return { return {
resourceKey, resourceKey,
resourceType, resourceType,
type: AddBookmark.ACTION, type: AddBookmark.REQUEST,
} }
} };
export function removeBookmark(resourceKey: string, resourceType: string): RemoveBookmarkRequest { export function removeBookmark(resourceKey: string, resourceType: string): RemoveBookmarkRequest {
return { return {
resourceKey, resourceKey,
resourceType, resourceType,
type: RemoveBookmark.ACTION, type: RemoveBookmark.REQUEST,
} }
} };
export function getBookmarks(): GetBookmarksRequest { export function getBookmarks(): GetBookmarksRequest {
return { return {
type: GetBookmarks.ACTION type: GetBookmarks.REQUEST,
} }
} };
export function getBookmarksForUser(userId: string): GetBookmarksForUserRequest { export function getBookmarksForUser(userId: string): GetBookmarksForUserRequest {
return { return {
userId, userId,
type: GetBookmarksForUser.ACTION, type: GetBookmarksForUser.REQUEST,
} }
} };
export interface BookmarkReducerState { /* REDUCER */
export interface BookmarkReducerState {
myBookmarks: Bookmark[]; myBookmarks: Bookmark[];
myBookmarksIsLoaded: boolean; myBookmarksIsLoaded: boolean;
bookmarksForUser: Bookmark[]; bookmarksForUser: Bookmark[];
} }
const initialState: BookmarkReducerState = { export const initialState: BookmarkReducerState = {
myBookmarks: [], myBookmarks: [],
myBookmarksIsLoaded: false, myBookmarksIsLoaded: false,
bookmarksForUser: [], bookmarksForUser: [],
}; };
export default function reducer(state: BookmarkReducerState = initialState, action: BookmarkReducerAction): BookmarkReducerState { export default function reducer(state: BookmarkReducerState = initialState, action): BookmarkReducerState {
switch(action.type) { switch(action.type) {
case RemoveBookmark.SUCCESS: case RemoveBookmark.SUCCESS:
const { resourceKey } = action.payload; const { resourceKey } = (<RemoveBookmarkResponse>action).payload;
return { return {
...state, ...state,
myBookmarks: state.myBookmarks.filter((bookmark) => bookmark.key !== resourceKey) myBookmarks: state.myBookmarks.filter((bookmark) => bookmark.key !== resourceKey)
}; };
case AddBookmark.SUCCESS: case AddBookmark.SUCCESS:
case GetBookmarks.SUCCESS: case GetBookmarks.SUCCESS:
return { return {
...state, ...state,
myBookmarks: action.payload, myBookmarks: (<GetBookmarksResponse>action).payload.bookmarks,
myBookmarksIsLoaded: true, myBookmarksIsLoaded: true,
}; };
case AddBookmark.FAILURE: case AddBookmark.FAILURE:
......
import { call, put, takeEvery } from 'redux-saga/effects';
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects';
import { import {
AddBookmark, AddBookmark,
AddBookmarkRequest, AddBookmarkRequest,
GetBookmarks, GetBookmarks,
GetBookmarksRequest,
GetBookmarksForUser, GetBookmarksForUser,
GetBookmarksForUserRequest, GetBookmarksForUserRequest,
GetBookmarksRequest,
RemoveBookmark, RemoveBookmark,
RemoveBookmarkRequest RemoveBookmarkRequest
} from "./types"; } from './types';
import { import {
addBookmark, addBookmark,
removeBookmark, removeBookmark,
getBookmarks, getBookmarks,
} from "./api/v0"; } from './api/v0';
// AddBookmarks
export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator { export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator {
let response; let response;
const { resourceKey, resourceType } = action; const { resourceKey, resourceType } = action;
...@@ -29,17 +27,16 @@ export function* addBookmarkWorker(action: AddBookmarkRequest): SagaIterator { ...@@ -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. // TODO - Consider adding the newly bookmarked resource directly to local store. This would save a round trip.
response = yield call(getBookmarks); response = yield call(getBookmarks);
yield put({ type: AddBookmark.SUCCESS, payload: response.bookmarks }); yield put({ type: AddBookmark.SUCCESS, payload: { bookmarks: response.bookmarks } });
} catch(e) { } catch(e) {
yield put({ type: AddBookmark.FAILURE, payload: response }); yield put({ type: AddBookmark.FAILURE, payload: { bookmarks: [] } });
} }
} }
export function* addBookmarkWatcher(): SagaIterator { export function* addBookmarkWatcher(): SagaIterator {
yield takeEvery(AddBookmark.ACTION , addBookmarkWorker) yield takeEvery(AddBookmark.REQUEST , addBookmarkWorker)
} }
// RemoveBookmarks
export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterator { export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterator {
let response; let response;
const { resourceKey, resourceType } = action; const { resourceKey, resourceType } = action;
...@@ -47,30 +44,28 @@ export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterat ...@@ -47,30 +44,28 @@ export function* removeBookmarkWorker(action: RemoveBookmarkRequest): SagaIterat
response = yield call(removeBookmark, resourceKey, resourceType); response = yield call(removeBookmark, resourceKey, resourceType);
yield put({ type: RemoveBookmark.SUCCESS, payload: { resourceKey, resourceType }}); yield put({ type: RemoveBookmark.SUCCESS, payload: { resourceKey, resourceType }});
} catch(e) { } catch(e) {
yield put({ type: RemoveBookmark.FAILURE, payload: response }); yield put({ type: RemoveBookmark.FAILURE, payload: { resourceKey, resourceType } });
} }
} }
export function* removeBookmarkWatcher(): SagaIterator { export function* removeBookmarkWatcher(): SagaIterator {
yield takeEvery(RemoveBookmark.ACTION , removeBookmarkWorker) yield takeEvery(RemoveBookmark.REQUEST , removeBookmarkWorker)
} }
// GetBookmarks
export function* getBookmarksWorker(action: GetBookmarksRequest): SagaIterator { export function* getBookmarksWorker(action: GetBookmarksRequest): SagaIterator {
let response; let response;
try { try {
response = yield call(getBookmarks); response = yield call(getBookmarks);
yield put({ type: GetBookmarks.SUCCESS, payload: response.bookmarks }); yield put({ type: GetBookmarks.SUCCESS, payload: { bookmarks: response.bookmarks } });
} catch(e) { } catch(e) {
yield put({ type: GetBookmarks.FAILURE, payload: response }); yield put({ type: GetBookmarks.FAILURE, payload: { bookmarks: [] } });
} }
} }
export function* getBookmarkskWatcher(): SagaIterator { export function* getBookmarksWatcher(): SagaIterator {
yield takeEvery(GetBookmarks.ACTION, getBookmarksWorker) yield takeEvery(GetBookmarks.REQUEST, getBookmarksWorker)
} }
// GetBookmarksForUser
export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): SagaIterator { export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): SagaIterator {
let response; let response;
const { userId } = action; const { userId } = action;
...@@ -80,9 +75,9 @@ export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): S ...@@ -80,9 +75,9 @@ export function* getBookmarkForUserWorker(action: GetBookmarksForUserRequest): S
yield put({ type: GetBookmarksForUser.SUCCESS, payload: { userId, bookmarks: response.bookmarks } }); yield put({ type: GetBookmarksForUser.SUCCESS, payload: { userId, bookmarks: response.bookmarks } });
} catch(e) { } catch(e) {
yield put({ type: GetBookmarksForUser.FAILURE, payload: response }); yield put({ type: GetBookmarksForUser.FAILURE, payload: { userId, bookmarks: [] } });
} }
} }
export function* getBookmarksForUserWatcher(): SagaIterator { export function* getBookmarksForUserWatcher(): SagaIterator {
yield takeEvery(GetBookmarksForUser.ACTION, getBookmarkForUserWorker) yield takeEvery(GetBookmarksForUser.REQUEST, getBookmarkForUserWorker)
} }
import { TableResource } from "components/common/ResourceListItem/types"; import { Bookmark } from 'interfaces';
export type Bookmark = TableResource;
// AddBookmark
export enum AddBookmark { export enum AddBookmark {
ACTION = 'amundsen/bookmark/ADD', REQUEST = 'amundsen/bookmark/ADD_REQUEST',
SUCCESS = 'amundsen/bookmark/ADD_SUCCESS', SUCCESS = 'amundsen/bookmark/ADD_SUCCESS',
FAILURE = 'amundsen/bookmark/ADD_FAILURE', FAILURE = 'amundsen/bookmark/ADD_FAILURE',
} }
export interface AddBookmarkRequest { export interface AddBookmarkRequest {
type: AddBookmark.ACTION; type: AddBookmark.REQUEST;
resourceKey: string; resourceKey: string;
resourceType: string; resourceType: string;
} }
export interface AddBookmarkResponse { export interface AddBookmarkResponse {
type: AddBookmark.SUCCESS | AddBookmark.FAILURE; type: AddBookmark.SUCCESS | AddBookmark.FAILURE;
payload: Bookmark[]; payload: {
bookmarks: Bookmark[];
}
} }
// RemoveBookmark
export enum RemoveBookmark { export enum RemoveBookmark {
ACTION = 'amundsen/bookmark/REMOVE', REQUEST = 'amundsen/bookmark/REMOVE_REQUEST',
SUCCESS = 'amundsen/bookmark/REMOVE_SUCCESS', SUCCESS = 'amundsen/bookmark/REMOVE_SUCCESS',
FAILURE = 'amundsen/bookmark/REMOVE_FAILURE', FAILURE = 'amundsen/bookmark/REMOVE_FAILURE',
} }
export interface RemoveBookmarkRequest { export interface RemoveBookmarkRequest {
type: RemoveBookmark.ACTION; type: RemoveBookmark.REQUEST;
resourceKey: string; resourceKey: string;
resourceType: string; resourceType: string;
} }
...@@ -38,30 +35,30 @@ export interface RemoveBookmarkResponse { ...@@ -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 { export enum GetBookmarks {
ACTION = 'amundsen/bookmark/GET', REQUEST = 'amundsen/bookmark/GET_REQUEST',
SUCCESS = 'amundsen/bookmark/GET_SUCCESS', SUCCESS = 'amundsen/bookmark/GET_SUCCESS',
FAILURE = 'amundsen/bookmark/GET_FAILURE', FAILURE = 'amundsen/bookmark/GET_FAILURE',
} }
export interface GetBookmarksRequest { export interface GetBookmarksRequest {
type: GetBookmarks.ACTION; type: GetBookmarks.REQUEST;
} }
export interface GetBookmarksResponse { export interface GetBookmarksResponse {
type: GetBookmarks.SUCCESS | GetBookmarks.FAILURE; 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 { export enum GetBookmarksForUser {
ACTION = 'amundsen/bookmark/GET_FOR_USER', REQUEST = 'amundsen/bookmark/GET_FOR_USER_REQUEST',
SUCCESS = 'amundsen/bookmark/GET_FOR_USER_SUCCESS', SUCCESS = 'amundsen/bookmark/GET_FOR_USER_SUCCESS',
FAILURE = 'amundsen/bookmark/GET_FOR_USER_FAILURE', FAILURE = 'amundsen/bookmark/GET_FOR_USER_FAILURE',
} }
export interface GetBookmarksForUserRequest { export interface GetBookmarksForUserRequest {
type: GetBookmarksForUser.ACTION; type: GetBookmarksForUser.REQUEST;
userId: string; userId: string;
} }
export interface GetBookmarksForUserResponse { export interface GetBookmarksForUserResponse {
......
import { TableResource } from 'components/common/ResourceListItem/types'; import { TableResource } from 'interfaces';
export { TableResource }; export { TableResource };
/* API */ /* API */
......
import { all } from 'redux-saga/effects'; import { all } from 'redux-saga/effects';
// AnnouncementPage // AnnouncementPage
import { announcementsGetWatcher } from "./announcements/sagas"; import { getAnnouncementsWatcher } from "./announcements/sagas";
import { import {
addBookmarkWatcher, addBookmarkWatcher,
getBookmarksForUserWatcher, getBookmarksForUserWatcher,
getBookmarkskWatcher, getBookmarksWatcher,
removeBookmarkWatcher removeBookmarkWatcher
} from "ducks/bookmark/sagas"; } from "ducks/bookmark/sagas";
...@@ -39,11 +39,11 @@ import { getLoggedInUserWatcher, getUserWatcher } from "./user/sagas"; ...@@ -39,11 +39,11 @@ import { getLoggedInUserWatcher, getUserWatcher } from "./user/sagas";
export default function* rootSaga() { export default function* rootSaga() {
yield all([ yield all([
// AnnouncementPage // AnnouncementPage
announcementsGetWatcher(), getAnnouncementsWatcher(),
// Bookmarks // Bookmarks
addBookmarkWatcher(), addBookmarkWatcher(),
getBookmarksForUserWatcher(), getBookmarksForUserWatcher(),
getBookmarkskWatcher(), getBookmarksWatcher(),
removeBookmarkWatcher(), removeBookmarkWatcher(),
// FeedbackForm // FeedbackForm
submitFeedbackWatcher(), submitFeedbackWatcher(),
......
...@@ -10,7 +10,7 @@ import { ...@@ -10,7 +10,7 @@ import {
TableSearchResults, TableSearchResults,
UserSearchResults, UserSearchResults,
} from './types'; } from './types';
import { ResourceType } from 'components/common/ResourceListItem/types'; import { ResourceType } from 'interfaces';
export type SearchReducerAction = SearchAllResponse | SearchResourceResponse | SearchAllRequest | SearchResourceRequest; export type SearchReducerAction = SearchAllResponse | SearchResourceResponse | SearchAllRequest | SearchResourceRequest;
......
...@@ -4,7 +4,7 @@ import { ...@@ -4,7 +4,7 @@ import {
DashboardResource, DashboardResource,
TableResource, TableResource,
UserResource, UserResource,
} from 'components/common/ResourceListItem/types'; } from 'interfaces';
import { SearchReducerState } from './reducer'; import { SearchReducerState } from './reducer';
interface SearchResults<T extends Resource> { interface SearchResults<T extends Resource> {
......
import { PreviewData, PreviewQueryParams, TableMetadata, User } from 'components/TableDetail/types'; 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'; 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 MessageResponse = { msg: string };
type TableData = TableMetadata & { type TableData = TableMetadata & {
......
import { ActionLogParams, postActionLog } from "./log/api/v0"; 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 { export function sortTagsAlphabetical(a: Tag, b: Tag): number {
return a.tag_name.localeCompare(b.tag_name); return a.tag_name.localeCompare(b.tag_name);
......
import { GlobalState } from 'ducks/rootReducer'; import { GlobalState } from 'ducks/rootReducer';
import { SendingState } from 'components/Feedback/types'; import { SendingState } from 'components/Feedback/types';
import { ResourceType } from 'components/common/ResourceListItem/types'; import { ResourceType } from 'interfaces';
const globalState: GlobalState = { const globalState: GlobalState = {
announcements: { 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 @@ ...@@ -716,6 +716,18 @@
"es-abstract": "^1.7.0" "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": { "array-union": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
...@@ -5444,6 +5456,12 @@ ...@@ -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": { "fstream": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
...@@ -8627,6 +8645,12 @@ ...@@ -8627,6 +8645,12 @@
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true "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": { "json5": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
...@@ -8828,6 +8852,12 @@ ...@@ -8828,6 +8852,12 @@
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
"dev": true "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": { "lodash.isplainobject": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz",
...@@ -12113,6 +12143,20 @@ ...@@ -12113,6 +12143,20 @@
"integrity": "sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w==", "integrity": "sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w==",
"dev": true "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": { "reflect-metadata": {
"version": "0.1.12", "version": "0.1.12",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz",
...@@ -14917,6 +14961,41 @@ ...@@ -14917,6 +14961,41 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "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": { "util.promisify": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
......
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
"prettier": "^1.12.1", "prettier": "^1.12.1",
"redux-mock-store": "^1.5.3", "redux-mock-store": "^1.5.3",
"redux-saga": "^0.16.2", "redux-saga": "^0.16.2",
"redux-saga-test-plan": "^3.7.0",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"style-loader": "^0.20.3", "style-loader": "^0.20.3",
"terser-webpack-plugin": "^1.1.0", "terser-webpack-plugin": "^1.1.0",
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"components/*": [ "components/*" ], "components/*": [ "components/*" ],
"config/*": [ "config/*" ], "config/*": [ "config/*" ],
"ducks/*": [ "ducks/*" ], "ducks/*": [ "ducks/*" ],
"interfaces/*": [ "interfaces/*" ],
} }
}, },
"exclude": [ "exclude": [
......
...@@ -19,6 +19,7 @@ const config: webpack.Configuration = { ...@@ -19,6 +19,7 @@ const config: webpack.Configuration = {
components: path.join(__dirname, '/js/components'), components: path.join(__dirname, '/js/components'),
config: path.join(__dirname, '/js/config'), config: path.join(__dirname, '/js/config'),
ducks: path.join(__dirname, '/js/ducks'), ducks: path.join(__dirname, '/js/ducks'),
interfaces: path.join(__dirname, '/js/interfaces'),
}, },
extensions: ['.tsx', '.ts', '.js', '.jsx', '.css', '.scss'], 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