diff --git a/app/actions/filters.js b/app/actions/filters.js index 5bd1105f8..6880f2d60 100644 --- a/app/actions/filters.js +++ b/app/actions/filters.js @@ -5,7 +5,7 @@ export const createFilterRequest = ({ name, jql }) => ({ payload: { name, jql }, }); -export const updateFilterRequest = (oldFilter, newJQLString) => ({ +export const updateFilterRequest = ({ oldFilter, newJQLString }) => ({ type: types.UPDATE_FILTER_REQUEST, payload: { oldFilter, diff --git a/app/containers/Sidebar/IssuesSourcePicker/IssuesSourcePicker.jsx b/app/containers/Sidebar/IssuesSourcePicker/IssuesSourcePicker.jsx index 066e0704f..dc9f81575 100644 --- a/app/containers/Sidebar/IssuesSourcePicker/IssuesSourcePicker.jsx +++ b/app/containers/Sidebar/IssuesSourcePicker/IssuesSourcePicker.jsx @@ -21,11 +21,8 @@ import type { import { SingleSelect, - Flex, } from 'components'; -import { FieldTextStateless as FieldText } from '@atlaskit/field-text'; - import { getIssuesSourceOptions, getSelectedSprintOption, @@ -33,25 +30,17 @@ import { getIssuesSourceSelectedOption, getUiState, } from 'selectors'; + import { issuesActions, sprintsActions, - filtersActions, resourcesActions, uiActions, } from 'actions'; -import InlineDialog from '@atlaskit/inline-dialog'; - -import Button from '@atlaskit/button'; - -import * as R from 'ramda'; - -import { - InputLabel, - IssuesSourceContainer, -} from './styled'; +import JQLFilter from './JQLFilter'; +import { IssuesSourceContainer } from './styled'; type Props = { options: Array, @@ -61,10 +50,6 @@ type Props = { projectsFetching: boolean, sprintsFetching: boolean, selectedSourceType: string, - newJQLFilterName: string | null, - newJQLFilterValue: string | null, - newJQLFilterErrors: Array, - saveFilterDialogOpen: boolean, dispatch: Dispatch, }; @@ -76,10 +61,6 @@ const IssuesSourcePicker: StatelessFunctionalComponent = ({ projectsFetching, sprintsFetching, selectedSourceType, - newJQLFilterName, - newJQLFilterValue, - newJQLFilterErrors, - saveFilterDialogOpen, dispatch, }: Props): Node => @@ -138,75 +119,7 @@ const IssuesSourcePicker: StatelessFunctionalComponent = ({ /> } {console.log(selectedOption)} - { (selectedSourceType === 'filter') && - - 0} - isLabelHidden - invalidMessage={newJQLFilterErrors.join(', ')} - onChange={(e) => { - if (e.target.value === R.path(['meta', 'filter', 'jql'], selectedOption)) { - dispatch(uiActions.setUiState('newJQLFilterValue', null)); - } else { - dispatch(uiActions.setUiState('newJQLFilterValue', e.target.value)); - } - }} - shouldFitContainer - /> - - dispatch(uiActions.setUiState('saveFilterDialogOpen', false))} - content={ - - - dispatch(uiActions.setUiState('newJQLFilterName', e.target.value))} - shouldFitContainer - /> - - - - } - > - - - - - - - } + {selectedSourceType === 'filter' && } ; function mapStateToProps(state) { @@ -214,10 +127,6 @@ function mapStateToProps(state) { options: getIssuesSourceOptions(state), selectedOption: getIssuesSourceSelectedOption(state), selectedSourceType: getUiState('issuesSourceType')(state), - newJQLFilterName: getUiState('newJQLFilterName')(state), - newJQLFilterValue: getUiState('newJQLFilterValue')(state), - newJQLFilterErrors: getUiState('newJQLFilterErrors')(state), - saveFilterDialogOpen: getUiState('saveFilterDialogOpen')(state), sprintsOptions: getSprintsOptions(state), selectedSprintOption: getSelectedSprintOption(state), diff --git a/app/containers/Sidebar/IssuesSourcePicker/JQLFilter.jsx b/app/containers/Sidebar/IssuesSourcePicker/JQLFilter.jsx new file mode 100644 index 000000000..dd5beef94 --- /dev/null +++ b/app/containers/Sidebar/IssuesSourcePicker/JQLFilter.jsx @@ -0,0 +1,147 @@ +// @flow +import React from 'react'; +import InlineDialog from '@atlaskit/inline-dialog'; +import { connect } from 'react-redux'; +import { FieldTextStateless as FieldText } from '@atlaskit/field-text'; +import Spinner from '@atlaskit/spinner'; +import Button from '@atlaskit/button'; +import { Flex } from 'components'; + +import { + getUiState, + getResourceStatus, +} from 'selectors'; + +import { + filtersActions, + uiActions, +} from 'actions'; + +import type { + Dispatch, + SelectedOption, +} from 'types'; + +import * as R from 'ramda'; + +import { InputLabel } from './styled'; + +type Props = { + newJQLFilterName: string | null, + newJQLFilterValue: string | null, + newJQLFilterErrors: Array, + newJQLFilterSaving: boolean, + newJQLFilterAdding: boolean, + saveFilterDialogOpen: boolean, + selectedFilter: SelectedOption, + dispatch: Dispatch, +} + +const JQLFilter = ({ + newJQLFilterName, + newJQLFilterValue, + newJQLFilterErrors, + newJQLFilterSaving, + newJQLFilterAdding, + saveFilterDialogOpen, + selectedFilter, + dispatch, +}: Props) => ( + + 0} + isLabelHidden + invalidMessage={newJQLFilterErrors.join(', ')} + onChange={(e) => { + dispatch(uiActions.setUiState('newJQLFilterErrors', [])); + if (e.target.value === R.path(['meta', 'filter', 'jql'], selectedFilter)) { + dispatch(uiActions.setUiState('newJQLFilterValue', null)); + } else { + dispatch(uiActions.setUiState('newJQLFilterValue', e.target.value)); + } + }} + shouldFitContainer + /> + + dispatch(uiActions.setUiState('saveFilterDialogOpen', false))} + content={ + + New filter name + + dispatch(uiActions.setUiState('newJQLFilterName', e.target.value))} + shouldFitContainer + /> + + + + } + > + + + + + + +); + +const connector = connect( + state => ({ + newJQLFilterName: getUiState('newJQLFilterName')(state), + newJQLFilterValue: getUiState('newJQLFilterValue')(state), + newJQLFilterSaving: getResourceStatus( + state, + 'filters.requests.updateFilter.status', + ).pending, + newJQLFilterAdding: getResourceStatus( + state, + 'filters.requests.createFilter.status', + ).pending, + newJQLFilterErrors: getUiState('newJQLFilterErrors')(state), + saveFilterDialogOpen: getUiState('saveFilterDialogOpen')(state), + }), + dispatch => ({ dispatch }), +); + +export default connector(JQLFilter); diff --git a/app/sagas/filters.js b/app/sagas/filters.js index c3a407298..5a8c7775b 100644 --- a/app/sagas/filters.js +++ b/app/sagas/filters.js @@ -7,9 +7,15 @@ import { import createActionCreators from 'redux-resource-action-creators'; -import { types, uiActions } from 'actions'; +import { + types, + uiActions, + resourcesActions, + issuesActions, +} from 'actions'; import * as Api from 'api'; +import * as R from 'ramda'; import { throwError, @@ -40,13 +46,13 @@ export function* fetchFilters(): Generator<*, *, *> { export function* createFilterFlow(): Generator<*, *, *> { while (true) { + const actions = createActionCreators('create', { + resourceName: 'filters', + request: 'createFilter', + list: 'allFilters', + }); try { const { payload: { name, jql } } = yield take(types.CREATE_FILTER_REQUEST); - const actions = createActionCreators('create', { - resourceName: 'filters', - request: 'createFilter', - list: 'allFilters', - }); const newFilter = { name, description: 'Filter created in Chronos desktop', @@ -56,17 +62,86 @@ export function* createFilterFlow(): Generator<*, *, *> { }; yield put(actions.pending()); const created = yield call(Api.createFilter, newFilter); - console.log(created); + yield fork(notify, { + title: `Created filter ${name}`, + }); yield put(actions.succeeded({ resources: [created], })); + yield put(uiActions.setUiState('saveFilterDialogOpen', false)); + yield put(uiActions.setUiState('issuesSourceId', created.id)); + yield put(uiActions.setUiState('filterStatusesIsFetched', false)); + yield put(resourcesActions.clearResourceList({ + resourceName: 'issues', + list: 'recentIssues', + })); + yield put(issuesActions.refetchIssuesRequest()); } catch (err) { + yield put(actions.failed()); + const errObj = JSON.parse(err); + if (errObj.body.errorMessages.length > 0) { + yield fork(notify, { + title: 'Failed to create filter', + description: `${errObj.body.errorMessages.length} errors`, + }); + yield put(uiActions.setUiState('newJQLFilterErrors', errObj.body.errorMessages)); + } else { + yield fork(notify, { + title: 'Failed to create filter', + description: R.values(errObj.body.errors).join('\n'), + }); + } + yield call(throwError, err); + } + } +} + +export function* updateFilterFlow(): Generator<*, *, *> { + while (true) { + const actions = createActionCreators('update', { + resourceName: 'filters', + request: 'updateFilter', + list: 'allFilters', + }); + try { + const { payload: { oldFilter, newJQLString } } = yield take(types.UPDATE_FILTER_REQUEST); + const newFilter = { + name: oldFilter.name, + description: oldFilter.description, + jql: newJQLString, + favourite: oldFilter.favourite, + favouritedCount: oldFilter.favouritedCount, + }; + yield put(actions.pending()); + const updated = yield call(Api.updateFilter, oldFilter.id, newFilter); yield fork(notify, { - title: 'Failed to create filter, check your permissions', + title: `Updated filter ${oldFilter.name}`, }); + yield put(uiActions.setUiState('newJQLFilterValue', null)); + yield put(actions.succeeded({ + resources: [updated], + })); + yield put(uiActions.setUiState('filterStatusesIsFetched', false)); + yield put(resourcesActions.clearResourceList({ + resourceName: 'issues', + list: 'recentIssues', + })); + yield put(issuesActions.refetchIssuesRequest()); + } catch (err) { + yield put(actions.failed()); const errObj = JSON.parse(err); - console.log(errObj); - yield put(uiActions.setUiState('newJQLFilterErrors', errObj.body.errorMessages)) + if (errObj.body.errorMessages.length > 0) { + yield fork(notify, { + title: 'Failed to update filter', + description: `${errObj.body.errorMessages.length} errors`, + }); + yield put(uiActions.setUiState('newJQLFilterErrors', errObj.body.errorMessages)); + } else { + yield fork(notify, { + title: 'Failed to update filter', + description: R.values(errObj.body.errors).join('\n'), + }); + } yield call(throwError, err); } } diff --git a/app/sagas/index.js b/app/sagas/index.js index 254971ce8..13ffc6b11 100644 --- a/app/sagas/index.js +++ b/app/sagas/index.js @@ -76,6 +76,7 @@ export default function* rootSaga(): Generator<*, void, *> { // filters fork(filtersSagas.createFilterFlow), + fork(filtersSagas.updateFilterFlow), // ui fork(uiSagas.watchUiStateChange), diff --git a/app/selectors/index.js b/app/selectors/index.js index 2ead84371..7a4361223 100644 --- a/app/selectors/index.js +++ b/app/selectors/index.js @@ -7,3 +7,5 @@ export * from './projects'; export * from './sprints'; export * from './issues'; export * from './timer'; + +export { getStatus as getResourceStatus } from 'redux-resource'; diff --git a/app/utils/api/filters.js b/app/utils/api/filters.js index d2a7f88db..b1f4a2464 100644 --- a/app/utils/api/filters.js +++ b/app/utils/api/filters.js @@ -7,3 +7,7 @@ export function fetchFilters(): Promise<*> { export function createFilter(filter): Promise<*> { return jira.client.filter.createFilter({ filter }); } + +export function updateFilter(filterId, filter): Promise<*> { + return jira.client.filter.updateFilter({ filterId, filter }); +};