diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-09-03 09:21:22 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-09-03 20:20:51 +0200 |
commit | f6fd6fb056ca6c3de3637cfeafb53c30815ee12d (patch) | |
tree | bdf6b3a660066ffe6aaf644f6969e4353a7e21c8 /server/sonar-web/src/main/js/store | |
parent | f332f24ea986de3267aedcc9ba8ec1441ff4226b (diff) | |
download | sonarqube-f6fd6fb056ca6c3de3637cfeafb53c30815ee12d.tar.gz sonarqube-f6fd6fb056ca6c3de3637cfeafb53c30815ee12d.zip |
finish typing redux store and simplify connected components (#675)
Diffstat (limited to 'server/sonar-web/src/main/js/store')
21 files changed, 828 insertions, 829 deletions
diff --git a/server/sonar-web/src/main/js/store/__tests__/__snapshots__/organizations-test.ts.snap b/server/sonar-web/src/main/js/store/__tests__/__snapshots__/organizations-test.ts.snap new file mode 100644 index 00000000000..bc2ba11d2bc --- /dev/null +++ b/server/sonar-web/src/main/js/store/__tests__/__snapshots__/organizations-test.ts.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Reducer should create organization 1`] = ` +Object { + "byKey": Object { + "foo": Object { + "isAdmin": true, + "key": "foo", + "name": "foo", + }, + }, + "my": Array [ + "foo", + ], +} +`; + +exports[`Reducer should delete organization 1`] = ` +Object { + "byKey": Object {}, + "my": Array [], +} +`; + +exports[`Reducer should have initial state 1`] = ` +Object { + "byKey": Object {}, + "my": Array [], +} +`; + +exports[`Reducer should receive my organizations 1`] = ` +Object { + "byKey": Object { + "bar": Object { + "key": "bar", + "name": "Bar", + }, + "foo": Object { + "key": "foo", + "name": "Foo", + }, + }, + "my": Array [ + "foo", + "bar", + ], +} +`; + +exports[`Reducer should receive organizations 1`] = ` +Object { + "byKey": Object { + "bar": Object { + "key": "bar", + "name": "Bar", + }, + "foo": Object { + "key": "foo", + "name": "Foo", + }, + }, + "my": Array [], +} +`; + +exports[`Reducer should receive organizations 2`] = ` +Object { + "byKey": Object { + "bar": Object { + "key": "bar", + "name": "Bar", + }, + "foo": Object { + "key": "foo", + "name": "Qwe", + }, + }, + "my": Array [], +} +`; + +exports[`Reducer should update organization 1`] = ` +Object { + "byKey": Object { + "foo": Object { + "description": "description", + "isAdmin": true, + "key": "foo", + "name": "bar", + }, + }, + "my": Array [ + "foo", + ], +} +`; diff --git a/server/sonar-web/src/main/js/store/__tests__/organizations-test.ts b/server/sonar-web/src/main/js/store/__tests__/organizations-test.ts new file mode 100644 index 00000000000..41233545f96 --- /dev/null +++ b/server/sonar-web/src/main/js/store/__tests__/organizations-test.ts @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import reducer, { + getOrganizationByKey, + areThereCustomOrganizations, + getMyOrganizations, + State, + receiveOrganizations, + receiveMyOrganizations, + createOrganization, + updateOrganization, + deleteOrganization +} from '../organizations'; + +const state0: State = { byKey: {}, my: [] }; + +describe('Reducer', () => { + it('should have initial state', () => { + // @ts-ignore `undefined` is passed when the redux store is created, + // however should not be allowed by typings + expect(reducer(undefined, {})).toMatchSnapshot(); + }); + + it('should receive organizations', () => { + const state1 = reducer( + state0, + receiveOrganizations([{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]) + ); + expect(state1).toMatchSnapshot(); + + const state2 = reducer(state1, receiveOrganizations([{ key: 'foo', name: 'Qwe' }])); + expect(state2).toMatchSnapshot(); + }); + + it('should receive my organizations', () => { + const state1 = reducer( + state0, + receiveMyOrganizations([{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]) + ); + expect(state1).toMatchSnapshot(); + }); + + it('should create organization', () => { + const state1 = reducer(state0, createOrganization({ key: 'foo', name: 'foo' })); + expect(state1).toMatchSnapshot(); + }); + + it('should update organization', () => { + const state1 = reducer(state0, createOrganization({ key: 'foo', name: 'foo' })); + const state2 = reducer( + state1, + updateOrganization('foo', { name: 'bar', description: 'description' }) + ); + expect(state2).toMatchSnapshot(); + }); + + it('should delete organization', () => { + const state1 = reducer(state0, createOrganization({ key: 'foo', name: 'foo' })); + const state2 = reducer(state1, deleteOrganization('foo')); + expect(state2).toMatchSnapshot(); + }); +}); + +describe('Selectors', () => { + it('getOrganizationByKey', () => { + const foo = { key: 'foo', name: 'Foo' }; + const state = { ...state0, byKey: { foo } }; + expect(getOrganizationByKey(state, 'foo')).toBe(foo); + expect(getOrganizationByKey(state, 'bar')).toBeFalsy(); + }); + + it('getMyOrganizations', () => { + expect(getMyOrganizations(state0)).toEqual([]); + + const foo = { key: 'foo', name: 'Foo' }; + expect(getMyOrganizations({ ...state0, byKey: { foo }, my: ['foo'] })).toEqual([foo]); + }); + + it('areThereCustomOrganizations', () => { + const foo = { key: 'foo', name: 'Foo' }; + const bar = { key: 'bar', name: 'Bar' }; + expect(areThereCustomOrganizations({ ...state0, byKey: {} })).toBe(false); + expect(areThereCustomOrganizations({ ...state0, byKey: { foo } })).toBe(false); + expect(areThereCustomOrganizations({ ...state0, byKey: { foo, bar } })).toBe(true); + }); +}); diff --git a/server/sonar-web/src/main/js/store/appState/duck.ts b/server/sonar-web/src/main/js/store/appState.ts index 79ada585879..19a5e546944 100644 --- a/server/sonar-web/src/main/js/store/appState/duck.ts +++ b/server/sonar-web/src/main/js/store/appState.ts @@ -17,44 +17,36 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Extension, AppState } from '../../app/types'; +import { ActionType } from './utils/actions'; +import { Extension, AppState } from '../app/types'; +import { EditionKey } from '../apps/marketplace/utils'; -interface SetAppStateAction { - type: 'SET_APP_STATE'; - appState: AppState; -} - -interface SetAdminPagesAction { - type: 'SET_ADMIN_PAGES'; - adminPages: Extension[]; -} - -interface RequireAuthorizationAction { - type: 'REQUIRE_AUTHORIZATION'; -} - -export type Action = SetAppStateAction | SetAdminPagesAction | RequireAuthorizationAction; +type Action = + | ActionType<typeof setAppState, 'SET_APP_STATE'> + | ActionType<typeof setAdminPages, 'SET_ADMIN_PAGES'> + | ActionType<typeof requireAuthorization, 'REQUIRE_AUTHORIZATION'>; -export function setAppState(appState: AppState): SetAppStateAction { - return { - type: 'SET_APP_STATE', - appState - }; +export function setAppState(appState: AppState) { + return { type: 'SET_APP_STATE', appState }; } -export function setAdminPages(adminPages: Extension[]): SetAdminPagesAction { +export function setAdminPages(adminPages: Extension[]) { return { type: 'SET_ADMIN_PAGES', adminPages }; } -export function requireAuthorization(): RequireAuthorizationAction { +export function requireAuthorization() { return { type: 'REQUIRE_AUTHORIZATION' }; } const defaultValue: AppState = { authenticationError: false, authorizationError: false, + defaultOrganization: '', + edition: EditionKey.community, organizationsEnabled: false, - qualifiers: [] + productionDatabase: true, + qualifiers: [], + version: '' }; export default function(state: AppState = defaultValue, action: Action): AppState { diff --git a/server/sonar-web/src/main/js/store/globalMessages.ts b/server/sonar-web/src/main/js/store/globalMessages.ts new file mode 100644 index 00000000000..370ecb1bcc8 --- /dev/null +++ b/server/sonar-web/src/main/js/store/globalMessages.ts @@ -0,0 +1,102 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { uniqueId } from 'lodash'; +import { Dispatch } from 'redux'; +import { requireAuthorization } from './appState'; +import { Store } from './rootReducer'; +import { ActionType } from './utils/actions'; + +enum MessageLevel { + Error = 'ERROR', + Success = 'SUCCESS' +} + +interface Message { + id: string; + message: string; + level: MessageLevel; +} + +function addGlobalMessageActionCreator(id: string, message: string, level: MessageLevel) { + return { type: 'ADD_GLOBAL_MESSAGE', message, level, id }; +} + +export function closeGlobalMessage(id: string) { + return { type: 'CLOSE_GLOBAL_MESSAGE', id }; +} + +export function closeAllGlobalMessages(id: string) { + return { type: 'CLOSE_ALL_GLOBAL_MESSAGES', id }; +} + +type Action = + | ActionType<typeof addGlobalMessageActionCreator, 'ADD_GLOBAL_MESSAGE'> + | ActionType<typeof closeGlobalMessage, 'CLOSE_GLOBAL_MESSAGE'> + | ActionType<typeof closeAllGlobalMessages, 'CLOSE_ALL_GLOBAL_MESSAGES'> + | ActionType<typeof requireAuthorization, 'REQUIRE_AUTHORIZATION'>; + +function addGlobalMessage(message: string, level: MessageLevel) { + return (dispatch: Dispatch<Store>) => { + const id = uniqueId('global-message-'); + dispatch(addGlobalMessageActionCreator(id, message, level)); + setTimeout(() => dispatch(closeGlobalMessage(id)), 5000); + }; +} + +export function addGlobalErrorMessage(message: string) { + return addGlobalMessage(message, MessageLevel.Error); +} + +export function addGlobalSuccessMessage(message: string) { + return addGlobalMessage(message, MessageLevel.Success); +} + +export type State = Message[]; + +export default function(state: State = [], action: Action): State { + switch (action.type) { + case 'ADD_GLOBAL_MESSAGE': + return [{ id: action.id, message: action.message, level: action.level }]; + + case 'REQUIRE_AUTHORIZATION': + // FIXME l10n + return [ + { + id: uniqueId('global-message-'), + message: + 'You are not authorized to access this page. ' + + 'Please log in with more privileges and try again.', + level: MessageLevel.Error + } + ]; + + case 'CLOSE_GLOBAL_MESSAGE': + return state.filter(message => message.id !== action.id); + + case 'CLOSE_ALL_GLOBAL_MESSAGES': + return []; + default: + return state; + } +} + +export function getGlobalMessages(state: State) { + return state; +} diff --git a/server/sonar-web/src/main/js/store/globalMessages/duck.js b/server/sonar-web/src/main/js/store/globalMessages/duck.js deleted file mode 100644 index 81c4cba0ff6..00000000000 --- a/server/sonar-web/src/main/js/store/globalMessages/duck.js +++ /dev/null @@ -1,131 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import { uniqueId } from 'lodash'; - -/*:: -type Level = 'ERROR' | 'SUCCESS'; -*/ - -/*:: -type Message = { - id: string, - message: string, - level: Level -}; -*/ - -/*:: -export type State = Array<Message>; -*/ - -/*:: -type Action = Object; -*/ - -const ERROR = 'ERROR'; -const SUCCESS = 'SUCCESS'; - -/* Actions */ -const ADD_GLOBAL_MESSAGE = 'ADD_GLOBAL_MESSAGE'; -const CLOSE_GLOBAL_MESSAGE = 'CLOSE_GLOBAL_MESSAGE'; -const CLOSE_ALL_GLOBAL_MESSAGES = 'CLOSE_ALL_GLOBAL_MESSAGES'; - -function addGlobalMessageActionCreator( - id /*: string */, - message /*: string */, - level /*: Level */ -) { - return { - type: ADD_GLOBAL_MESSAGE, - message, - level, - id - }; -} - -export function closeGlobalMessage(id /*: string */) { - return { - type: CLOSE_GLOBAL_MESSAGE, - id - }; -} - -export function closeAllGlobalMessages(id /*: string */) { - return { - type: CLOSE_ALL_GLOBAL_MESSAGES, - id - }; -} - -function addGlobalMessage(message /*: string */, level /*: Level */) { - return function(dispatch /*: Function */) { - const id = uniqueId('global-message-'); - dispatch(addGlobalMessageActionCreator(id, message, level)); - setTimeout(() => dispatch(closeGlobalMessage(id)), 5000); - }; -} - -export function addGlobalErrorMessage(message /*: string */) { - return addGlobalMessage(message, ERROR); -} - -export function addGlobalSuccessMessage(message /*: string */) { - return addGlobalMessage(message, SUCCESS); -} - -/* Reducer */ -export default function(state /*: State */ = [], action /*: Action */ = {}) { - switch (action.type) { - case ADD_GLOBAL_MESSAGE: - return [ - { - id: action.id, - message: action.message, - level: action.level - } - ]; - - case 'REQUIRE_AUTHORIZATION': - // FIXME l10n - return [ - { - id: uniqueId('global-message-'), - message: - 'You are not authorized to access this page. ' + - 'Please log in with more privileges and try again.', - level: ERROR - } - ]; - - case CLOSE_GLOBAL_MESSAGE: - return state.filter(message => message.id !== action.id); - - case CLOSE_ALL_GLOBAL_MESSAGES: - return []; - default: - return state; - } -} - -/* Selectors */ -export function getGlobalMessages(state /*: State */) { - return state; -} diff --git a/server/sonar-web/src/main/js/store/languages/reducer.ts b/server/sonar-web/src/main/js/store/languages.ts index 50caf69c59b..2cc95fe3006 100644 --- a/server/sonar-web/src/main/js/store/languages/reducer.ts +++ b/server/sonar-web/src/main/js/store/languages.ts @@ -18,22 +18,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { keyBy } from 'lodash'; -import { RECEIVE_LANGUAGES } from './actions'; +import { ActionType } from './utils/actions'; + +export function receiveLanguages(languages: Array<{ key: string; name: string }>) { + return { type: 'RECEIVE_LANGUAGES', languages }; +} + +type Action = ActionType<typeof receiveLanguages, 'RECEIVE_LANGUAGES'>; export interface Languages { [key: string]: { key: string; name: string }; } -const reducer = (state: Languages = {}, action: any = {}) => { - if (action.type === RECEIVE_LANGUAGES) { +export default function(state: Languages = {}, action: Action): Languages { + if (action.type === 'RECEIVE_LANGUAGES') { return keyBy(action.languages, 'key'); } return state; -}; - -export default reducer; +} -export const getLanguages = (state: Languages) => state; +export function getLanguages(state: Languages) { + return state; +} -export const getLanguageByKey = (state: Languages, key: string) => state[key]; +export function getLanguageByKey(state: Languages, key: string) { + return state[key]; +} diff --git a/server/sonar-web/src/main/js/store/metrics/reducer.js b/server/sonar-web/src/main/js/store/metrics.ts index d2d5284cc3a..85098902b21 100644 --- a/server/sonar-web/src/main/js/store/metrics/reducer.js +++ b/server/sonar-web/src/main/js/store/metrics.ts @@ -17,27 +17,28 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import { combineReducers } from 'redux'; import { keyBy } from 'lodash'; -import { RECEIVE_METRICS } from './actions'; -/*:: import type { Metric } from './actions'; */ +import { combineReducers } from 'redux'; +import { ActionType } from './utils/actions'; +import { Metric } from '../app/types'; + +export function receiveMetrics(metrics: Metric[]) { + return { type: 'RECEIVE_METRICS', metrics }; +} -/*:: -type StateByKey = { [string]: Metric }; -type StateKeys = Array<string>; -type State = { byKey: StateByKey, keys: StateKeys }; -*/ +type Action = ActionType<typeof receiveMetrics, 'RECEIVE_METRICS'>; -const byKey = (state /*: StateByKey */ = {}, action = {}) => { - if (action.type === RECEIVE_METRICS) { +export type State = { byKey: { [key: string]: Metric }; keys: string[] }; + +const byKey = (state: State['byKey'] = {}, action: Action) => { + if (action.type === 'RECEIVE_METRICS') { return keyBy(action.metrics, 'key'); } return state; }; -const keys = (state /*: StateKeys */ = [], action = {}) => { - if (action.type === RECEIVE_METRICS) { +const keys = (state: State['keys'] = [], action: Action) => { + if (action.type === 'RECEIVE_METRICS') { return action.metrics.map(f => f.key); } @@ -46,5 +47,10 @@ const keys = (state /*: StateKeys */ = [], action = {}) => { export default combineReducers({ byKey, keys }); -export const getMetrics = (state /*: State */) => state.byKey; -export const getMetricsKey = (state /*: State */) => state.keys; +export function getMetrics(state: State) { + return state.byKey; +} + +export function getMetricsKey(state: State) { + return state.keys; +} diff --git a/server/sonar-web/src/main/js/store/metrics/actions.js b/server/sonar-web/src/main/js/store/metrics/actions.js deleted file mode 100644 index a271928ce3b..00000000000 --- a/server/sonar-web/src/main/js/store/metrics/actions.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -/*:: export type Metric = { - bestValue?: string, - custom?: boolean, - decimalScale?: number, - description?: string, - direction?: number, - domain?: string, - hidden?: boolean, - key: string, - name: string, - qualitative?: boolean, - type: string -}; */ - -export const RECEIVE_METRICS = 'RECEIVE_METRICS'; - -export const receiveMetrics = (metrics /*: Array<Metric> */) => ({ - type: RECEIVE_METRICS, - metrics -}); diff --git a/server/sonar-web/src/main/js/store/organizations.ts b/server/sonar-web/src/main/js/store/organizations.ts new file mode 100644 index 00000000000..6b1dfdcc28c --- /dev/null +++ b/server/sonar-web/src/main/js/store/organizations.ts @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { combineReducers } from 'redux'; +import { omit, uniq, without } from 'lodash'; +import { ActionType } from './utils/actions'; +import { Organization, OrganizationBase } from '../app/types'; + +type ReceiveOrganizationsAction = + | ActionType<typeof receiveOrganizations, 'RECEIVE_ORGANIZATIONS'> + | ActionType<typeof receiveMyOrganizations, 'RECEIVE_MY_ORGANIZATIONS'>; + +type Action = + | ReceiveOrganizationsAction + | ActionType<typeof createOrganization, 'CREATE_ORGANIZATION'> + | ActionType<typeof updateOrganization, 'UPDATE_ORGANIZATION'> + | ActionType<typeof deleteOrganization, 'DELETE_ORGANIZATION'>; + +export interface State { + byKey: { [key: string]: Organization }; + my: string[]; +} + +export function receiveOrganizations(organizations: Organization[]) { + return { type: 'RECEIVE_ORGANIZATIONS', organizations }; +} + +export function receiveMyOrganizations(organizations: Organization[]) { + return { type: 'RECEIVE_MY_ORGANIZATIONS', organizations }; +} + +export function createOrganization(organization: Organization) { + return { type: 'CREATE_ORGANIZATION', organization }; +} + +export function updateOrganization(key: string, changes: OrganizationBase) { + return { type: 'UPDATE_ORGANIZATION', key, changes }; +} + +export function deleteOrganization(key: string) { + return { type: 'DELETE_ORGANIZATION', key }; +} + +function onReceiveOrganizations(state: State['byKey'], action: ReceiveOrganizationsAction) { + const nextState = { ...state }; + action.organizations.forEach(organization => { + nextState[organization.key] = { ...state[organization.key], ...organization }; + }); + return nextState; +} + +function byKey(state: State['byKey'] = {}, action: Action): State['byKey'] { + switch (action.type) { + case 'RECEIVE_ORGANIZATIONS': + case 'RECEIVE_MY_ORGANIZATIONS': + return onReceiveOrganizations(state, action); + case 'CREATE_ORGANIZATION': + return { ...state, [action.organization.key]: { ...action.organization, isAdmin: true } }; + case 'UPDATE_ORGANIZATION': + return { + ...state, + [action.key]: { + ...state[action.key], + key: action.key, + ...action.changes + } + }; + case 'DELETE_ORGANIZATION': + return omit(state, action.key); + default: + return state; + } +} + +function my(state: State['my'] = [], action: Action): State['my'] { + switch (action.type) { + case 'RECEIVE_MY_ORGANIZATIONS': + return uniq([...state, ...action.organizations.map(o => o.key)]); + case 'CREATE_ORGANIZATION': + return uniq([...state, action.organization.key]); + case 'DELETE_ORGANIZATION': + return without(state, action.key); + default: + return state; + } +} + +export default combineReducers({ byKey, my }); + +export function getOrganizationByKey(state: State, key: string) { + return state.byKey[key]; +} + +export function getMyOrganizations(state: State) { + return state.my.map(key => getOrganizationByKey(state, key)); +} + +export function areThereCustomOrganizations(state: State) { + return Object.keys(state.byKey).length > 1; +} diff --git a/server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.ts.snap b/server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.ts.snap deleted file mode 100644 index c5ad9b73625..00000000000 --- a/server/sonar-web/src/main/js/store/organizations/__tests__/__snapshots__/duck-test.ts.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Reducer should have initial state 1`] = ` -Object { - "byKey": Object {}, - "groups": Object {}, - "my": Array [], -} -`; - -exports[`Reducer should receive organizations 1`] = ` -Object { - "byKey": Object { - "bar": Object { - "key": "bar", - "name": "Bar", - }, - "foo": Object { - "key": "foo", - "name": "Foo", - }, - }, - "groups": Object {}, - "my": Array [], -} -`; - -exports[`Reducer should receive organizations 2`] = ` -Object { - "byKey": Object { - "bar": Object { - "key": "bar", - "name": "Bar", - }, - "foo": Object { - "key": "foo", - "name": "Qwe", - }, - }, - "groups": Object {}, - "my": Array [], -} -`; diff --git a/server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.ts b/server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.ts deleted file mode 100644 index e9321e29f90..00000000000 --- a/server/sonar-web/src/main/js/store/organizations/__tests__/duck-test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import organizations, { getOrganizationByKey, areThereCustomOrganizations } from '../duck'; - -const state0 = { byKey: {}, my: [], groups: {} }; - -describe('Reducer', () => { - it('should have initial state', () => { - expect((organizations as any)(undefined, {})).toMatchSnapshot(); - }); - - it('should receive organizations', () => { - const action1 = { - type: 'RECEIVE_ORGANIZATIONS', - organizations: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }] - }; - const state1 = organizations(state0, action1); - expect(state1).toMatchSnapshot(); - - const action2 = { - type: 'RECEIVE_ORGANIZATIONS', - organizations: [{ key: 'foo', name: 'Qwe' }] - }; - const state2 = organizations(state1, action2); - expect(state2).toMatchSnapshot(); - }); -}); - -describe('Selectors', () => { - it('getOrganizationByKey', () => { - const foo = { key: 'foo', name: 'Foo' }; - const state = { ...state0, byKey: { foo } }; - expect(getOrganizationByKey(state, 'foo')).toBe(foo); - expect(getOrganizationByKey(state, 'bar')).toBeFalsy(); - }); - - it('areThereCustomOrganizations', () => { - const foo = { key: 'foo', name: 'Foo' }; - const bar = { key: 'bar', name: 'Bar' }; - expect(areThereCustomOrganizations({ ...state0, byKey: {} })).toBe(false); - expect(areThereCustomOrganizations({ ...state0, byKey: { foo } })).toBe(false); - expect(areThereCustomOrganizations({ ...state0, byKey: { foo, bar } })).toBe(true); - }); -}); diff --git a/server/sonar-web/src/main/js/store/organizations/duck.ts b/server/sonar-web/src/main/js/store/organizations/duck.ts deleted file mode 100644 index 5f01ac95261..00000000000 --- a/server/sonar-web/src/main/js/store/organizations/duck.ts +++ /dev/null @@ -1,199 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { combineReducers } from 'redux'; -import { omit, uniq, without } from 'lodash'; -import { Group, Organization, OrganizationBase } from '../../app/types'; - -interface ReceiveOrganizationsAction { - type: 'RECEIVE_ORGANIZATIONS'; - organizations: Organization[]; -} - -interface ReceiveMyOrganizationsAction { - type: 'RECEIVE_MY_ORGANIZATIONS'; - organizations: Organization[]; -} - -interface ReceiveOrganizationGroups { - type: 'RECEIVE_ORGANIZATION_GROUPS'; - key: string; - groups: Group[]; -} - -interface CreateOrganizationAction { - type: 'CREATE_ORGANIZATION'; - organization: Organization; -} - -interface UpdateOrganizationAction { - type: 'UPDATE_ORGANIZATION'; - key: string; - changes: {}; -} - -interface DeleteOrganizationAction { - type: 'DELETE_ORGANIZATION'; - key: string; -} - -type Action = - | ReceiveOrganizationsAction - | ReceiveMyOrganizationsAction - | ReceiveOrganizationGroups - | CreateOrganizationAction - | UpdateOrganizationAction - | DeleteOrganizationAction; - -interface ByKeyState { - [key: string]: Organization; -} - -interface GroupsState { - [key: string]: Group[]; -} - -type MyState = string[]; - -interface State { - byKey: ByKeyState; - my: MyState; - groups: GroupsState; -} - -export function receiveOrganizations(organizations: Organization[]): ReceiveOrganizationsAction { - return { - type: 'RECEIVE_ORGANIZATIONS', - organizations - }; -} - -export function receiveMyOrganizations( - organizations: Organization[] -): ReceiveMyOrganizationsAction { - return { - type: 'RECEIVE_MY_ORGANIZATIONS', - organizations - }; -} - -export function receiveOrganizationGroups(key: string, groups: Group[]): ReceiveOrganizationGroups { - return { - type: 'RECEIVE_ORGANIZATION_GROUPS', - key, - groups - }; -} - -export function createOrganization(organization: Organization): CreateOrganizationAction { - return { - type: 'CREATE_ORGANIZATION', - organization - }; -} - -export function updateOrganization( - key: string, - changes: OrganizationBase -): UpdateOrganizationAction { - return { - type: 'UPDATE_ORGANIZATION', - key, - changes - }; -} - -export function deleteOrganization(key: string): DeleteOrganizationAction { - return { - type: 'DELETE_ORGANIZATION', - key - }; -} - -function onReceiveOrganizations( - state: ByKeyState, - action: ReceiveOrganizationsAction | ReceiveMyOrganizationsAction -): ByKeyState { - const nextState = { ...state }; - action.organizations.forEach(organization => { - nextState[organization.key] = { ...state[organization.key], ...organization }; - }); - return nextState; -} - -function byKey(state: ByKeyState = {}, action: Action) { - switch (action.type) { - case 'RECEIVE_ORGANIZATIONS': - case 'RECEIVE_MY_ORGANIZATIONS': - return onReceiveOrganizations(state, action); - case 'CREATE_ORGANIZATION': - return { ...state, [action.organization.key]: { ...action.organization, isAdmin: true } }; - case 'UPDATE_ORGANIZATION': - return { - ...state, - [action.key]: { - ...state[action.key], - key: action.key, - ...action.changes - } - }; - case 'DELETE_ORGANIZATION': - return omit(state, action.key); - default: - return state; - } -} - -function my(state: MyState = [], action: Action) { - switch (action.type) { - case 'RECEIVE_MY_ORGANIZATIONS': - return uniq([...state, ...action.organizations.map(o => o.key)]); - case 'CREATE_ORGANIZATION': - return uniq([...state, action.organization.key]); - case 'DELETE_ORGANIZATION': - return without(state, action.key); - default: - return state; - } -} - -function groups(state: GroupsState = {}, action: Action) { - if (action.type === 'RECEIVE_ORGANIZATION_GROUPS') { - return { ...state, [action.key]: action.groups }; - } - return state; -} - -export default combineReducers<State>({ byKey, my, groups }); - -export function getOrganizationByKey(state: State, key: string): Organization | undefined { - return state.byKey[key]; -} - -export function getOrganizationGroupsByKey(state: State, key: string): Group[] { - return state.groups[key] || []; -} - -export function getMyOrganizations(state: State): Organization[] { - return state.my.map(key => getOrganizationByKey(state, key) as Organization); -} - -export function areThereCustomOrganizations(state: State): boolean { - return Object.keys(state.byKey).length > 1; -} diff --git a/server/sonar-web/src/main/js/store/rootActions.js b/server/sonar-web/src/main/js/store/rootActions.js deleted file mode 100644 index c49e3921c6c..00000000000 --- a/server/sonar-web/src/main/js/store/rootActions.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { setAppState } from './appState/duck'; -import { receiveOrganizations } from './organizations/duck'; -import { receiveLanguages } from './languages/actions'; -import { receiveMetrics } from './metrics/actions'; -import { addGlobalErrorMessage } from './globalMessages/duck'; -import { getLanguages } from '../api/languages'; -import { getGlobalNavigation } from '../api/nav'; -import * as auth from '../api/auth'; -import { getOrganizations } from '../api/organizations'; -import { getAllMetrics } from '../api/metrics'; -import { parseError } from '../helpers/request'; - -export const onFail = dispatch => error => - parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); - -export const fetchLanguages = () => dispatch => - getLanguages().then(languages => dispatch(receiveLanguages(languages)), onFail(dispatch)); - -export const fetchMetrics = () => dispatch => - getAllMetrics().then(metrics => dispatch(receiveMetrics(metrics)), onFail(dispatch)); - -export const fetchOrganizations = (organizations /*: Array<string> | void */) => dispatch => - getOrganizations({ organizations: organizations && organizations.join() }).then( - r => dispatch(receiveOrganizations(r.organizations)), - onFail(dispatch) - ); - -export const doLogin = (login, password) => dispatch => - auth.login(login, password).then( - () => { - /* everything is fine */ - }, - () => { - dispatch(addGlobalErrorMessage('Authentication failed')); - return Promise.reject(); - } - ); - -export const doLogout = () => dispatch => - auth.logout().then( - () => { - /* everything is fine */ - }, - () => { - dispatch(addGlobalErrorMessage('Logout failed')); - return Promise.reject(); - } - ); diff --git a/server/sonar-web/src/main/js/store/rootActions.ts b/server/sonar-web/src/main/js/store/rootActions.ts new file mode 100644 index 00000000000..52ab8e776b6 --- /dev/null +++ b/server/sonar-web/src/main/js/store/rootActions.ts @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { Dispatch } from 'redux'; +import { addGlobalErrorMessage } from './globalMessages'; +import { receiveLanguages } from './languages'; +import { receiveMetrics } from './metrics'; +import { receiveOrganizations } from './organizations'; +import { Store } from './rootReducer'; +import * as auth from '../api/auth'; +import { getLanguages } from '../api/languages'; +import { getAllMetrics } from '../api/metrics'; +import { getOrganizations } from '../api/organizations'; + +export function fetchLanguages() { + return (dispatch: Dispatch<Store>) => { + getLanguages().then(languages => dispatch(receiveLanguages(languages)), () => {}); + }; +} + +export function fetchMetrics() { + return (dispatch: Dispatch<Store>) => { + getAllMetrics().then(metrics => dispatch(receiveMetrics(metrics)), () => {}); + }; +} + +export function fetchOrganizations(organizations: string[]) { + return (dispatch: Dispatch<Store>) => { + getOrganizations({ organizations: organizations && organizations.join() }).then( + r => dispatch(receiveOrganizations(r.organizations)), + () => {} + ); + }; +} + +export function doLogin(login: string, password: string) { + return (dispatch: Dispatch<Store>) => + auth.login(login, password).then( + () => { + /* everything is fine */ + }, + () => { + dispatch(addGlobalErrorMessage('Authentication failed')); + return Promise.reject(); + } + ); +} + +export function doLogout() { + return (dispatch: Dispatch<Store>) => + auth.logout().then( + () => { + /* everything is fine */ + }, + () => { + dispatch(addGlobalErrorMessage('Logout failed')); + return Promise.reject(); + } + ); +} diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js deleted file mode 100644 index 16d038d1e89..00000000000 --- a/server/sonar-web/src/main/js/store/rootReducer.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { combineReducers } from 'redux'; -import appState from './appState/duck'; -import users, * as fromUsers from './users/reducer'; -import languages, * as fromLanguages from './languages/reducer'; -import metrics, * as fromMetrics from './metrics/reducer'; -import organizations, * as fromOrganizations from './organizations/duck'; -import globalMessages, * as fromGlobalMessages from './globalMessages/duck'; -import permissionsApp, * as fromPermissionsApp from '../apps/permissions/shared/store/rootReducer'; -import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer'; -import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer'; - -export default combineReducers({ - appState, - globalMessages, - languages, - metrics, - organizations, - users, - - // apps - permissionsApp, - projectAdminApp, - settingsApp -}); - -export const getAppState = state => state.appState; - -export const getGlobalMessages = state => - fromGlobalMessages.getGlobalMessages(state.globalMessages); - -export const getLanguages = state => fromLanguages.getLanguages(state.languages); - -export const getCurrentUser = state => fromUsers.getCurrentUser(state.users); - -export const getUsersByLogins = (state, logins) => fromUsers.getUsersByLogins(state.users, logins); - -export const getMetrics = state => fromMetrics.getMetrics(state.metrics); - -export const getMetricsKey = state => fromMetrics.getMetricsKey(state.metrics); - -export const getOrganizationByKey = (state, key) => - fromOrganizations.getOrganizationByKey(state.organizations, key); - -export const getOrganizationGroupsByKey = (state, key) => - fromOrganizations.getOrganizationGroupsByKey(state.organizations, key); - -export const getMyOrganizations = state => - fromOrganizations.getMyOrganizations(state.organizations); - -export const areThereCustomOrganizations = state => getAppState(state).organizationsEnabled; - -export const getPermissionsAppUsers = state => fromPermissionsApp.getUsers(state.permissionsApp); - -export const getPermissionsAppGroups = state => fromPermissionsApp.getGroups(state.permissionsApp); - -export const isPermissionsAppLoading = state => fromPermissionsApp.isLoading(state.permissionsApp); - -export const getPermissionsAppQuery = state => fromPermissionsApp.getQuery(state.permissionsApp); - -export const getPermissionsAppFilter = state => fromPermissionsApp.getFilter(state.permissionsApp); - -export const getPermissionsAppSelectedPermission = state => - fromPermissionsApp.getSelectedPermission(state.permissionsApp); - -export const getPermissionsAppError = state => fromPermissionsApp.getError(state.permissionsApp); - -export const getGlobalSettingValue = (state, key) => - fromSettingsApp.getValue(state.settingsApp, key); - -export const getSettingsAppDefinition = (state, key) => - fromSettingsApp.getDefinition(state.settingsApp, key); - -export const getSettingsAppAllCategories = state => - fromSettingsApp.getAllCategories(state.settingsApp); - -export const getSettingsAppDefaultCategory = state => - fromSettingsApp.getDefaultCategory(state.settingsApp); - -export const getSettingsAppSettingsForCategory = (state, category, componentKey) => - fromSettingsApp.getSettingsForCategory(state.settingsApp, category, componentKey); - -export const getSettingsAppChangedValue = (state, key) => - fromSettingsApp.getChangedValue(state.settingsApp, key); - -export const isSettingsAppLoading = (state, key) => - fromSettingsApp.isLoading(state.settingsApp, key); - -export const getSettingsAppValidationMessage = (state, key) => - fromSettingsApp.getValidationMessage(state.settingsApp, key); - -export const getSettingsAppEncryptionState = state => - fromSettingsApp.getEncryptionState(state.settingsApp); - -export const getProjectAdminProjectModules = (state, projectKey) => - fromProjectAdminApp.getProjectModules(state.projectAdminApp, projectKey); diff --git a/server/sonar-web/src/main/js/store/rootReducer.ts b/server/sonar-web/src/main/js/store/rootReducer.ts new file mode 100644 index 00000000000..8dfc49d857f --- /dev/null +++ b/server/sonar-web/src/main/js/store/rootReducer.ts @@ -0,0 +1,166 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { combineReducers } from 'redux'; +import appState from './appState'; +import globalMessages, * as fromGlobalMessages from './globalMessages'; +import languages, * as fromLanguages from './languages'; +import metrics, * as fromMetrics from './metrics'; +import organizations, * as fromOrganizations from './organizations'; +import users, * as fromUsers from './users'; +import { AppState } from '../app/types'; +import permissionsApp, * as fromPermissionsApp from '../apps/permissions/shared/store/rootReducer'; +import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer'; +import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer'; + +export type Store = { + appState: AppState; + globalMessages: fromGlobalMessages.State; + languages: fromLanguages.Languages; + metrics: fromMetrics.State; + organizations: fromOrganizations.State; + users: fromUsers.State; + + // apps + permissionsApp: any; + projectAdminApp: any; + settingsApp: any; +}; + +export default combineReducers<Store>({ + appState, + globalMessages, + languages, + metrics, + organizations, + users, + + // apps + permissionsApp, + projectAdminApp, + settingsApp +}); + +export function getAppState(state: Store) { + return state.appState; +} + +export function getGlobalMessages(state: Store) { + return fromGlobalMessages.getGlobalMessages(state.globalMessages); +} + +export function getLanguages(state: Store) { + return fromLanguages.getLanguages(state.languages); +} + +export function getCurrentUser(state: Store) { + return fromUsers.getCurrentUser(state.users); +} + +export function getMetrics(state: Store) { + return fromMetrics.getMetrics(state.metrics); +} + +export function getMetricsKey(state: Store) { + return fromMetrics.getMetricsKey(state.metrics); +} + +export function getOrganizationByKey(state: Store, key: string) { + return fromOrganizations.getOrganizationByKey(state.organizations, key); +} + +export function getMyOrganizations(state: Store) { + return fromOrganizations.getMyOrganizations(state.organizations); +} + +export function areThereCustomOrganizations(state: Store) { + return getAppState(state).organizationsEnabled; +} + +export function getPermissionsAppUsers(state: Store) { + return fromPermissionsApp.getUsers(state.permissionsApp); +} + +export function getPermissionsAppGroups(state: Store) { + return fromPermissionsApp.getGroups(state.permissionsApp); +} + +export function isPermissionsAppLoading(state: Store) { + return fromPermissionsApp.isLoading(state.permissionsApp); +} + +export function getPermissionsAppQuery(state: Store) { + return fromPermissionsApp.getQuery(state.permissionsApp); +} + +export function getPermissionsAppFilter(state: Store) { + return fromPermissionsApp.getFilter(state.permissionsApp); +} + +export function getPermissionsAppSelectedPermission(state: Store) { + return fromPermissionsApp.getSelectedPermission(state.permissionsApp); +} + +export function getPermissionsAppError(state: Store) { + return fromPermissionsApp.getError(state.permissionsApp); +} + +export function getGlobalSettingValue(state: Store, key: string) { + return fromSettingsApp.getValue(state.settingsApp, key); +} + +export function getSettingsAppDefinition(state: Store, key: string) { + return fromSettingsApp.getDefinition(state.settingsApp, key); +} + +export function getSettingsAppAllCategories(state: Store) { + return fromSettingsApp.getAllCategories(state.settingsApp); +} + +export function getSettingsAppDefaultCategory(state: Store) { + return fromSettingsApp.getDefaultCategory(state.settingsApp); +} + +export function getSettingsAppSettingsForCategory( + state: Store, + category: string, + componentKey: string +) { + return fromSettingsApp.getSettingsForCategory(state.settingsApp, category, componentKey); +} + +export function getSettingsAppChangedValue(state: Store, key: string) { + return fromSettingsApp.getChangedValue(state.settingsApp, key); +} + +export function isSettingsAppLoading(state: Store, key: string) { + return fromSettingsApp.isLoading(state.settingsApp, key); +} + +export function getSettingsAppValidationMessage(state: Store, key: string) { + return fromSettingsApp.getValidationMessage(state.settingsApp, key); +} + +export function getSettingsAppEncryptionState(state: Store) { + return fromSettingsApp.getEncryptionState(state.settingsApp); +} + +export function getProjectAdminProjectModules(state: Store, projectKey: string) { + return fromProjectAdminApp.getProjectModules(state.projectAdminApp, projectKey); +} diff --git a/server/sonar-web/src/main/js/store/users.ts b/server/sonar-web/src/main/js/store/users.ts new file mode 100644 index 00000000000..969cf8a6f7e --- /dev/null +++ b/server/sonar-web/src/main/js/store/users.ts @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { uniq } from 'lodash'; +import { Dispatch, combineReducers } from 'redux'; +import { Store } from './rootReducer'; +import { ActionType } from './utils/actions'; +import * as api from '../api/users'; +import { CurrentUser, HomePage, isLoggedIn, LoggedInUser } from '../app/types'; + +export function receiveCurrentUser(user: CurrentUser) { + return { type: 'RECEIVE_CURRENT_USER', user }; +} + +export function skipOnboarding() { + return { type: 'SKIP_ONBOARDING' }; +} + +function setHomePageAction(homepage: HomePage) { + return { type: 'SET_HOMEPAGE', homepage }; +} + +export function setHomePage(homepage: HomePage) { + return (dispatch: Dispatch<Store>) => { + api.setHomePage(homepage).then( + () => { + dispatch(setHomePageAction(homepage)); + }, + () => {} + ); + }; +} + +type Action = + | ActionType<typeof receiveCurrentUser, 'RECEIVE_CURRENT_USER'> + | ActionType<typeof skipOnboarding, 'SKIP_ONBOARDING'> + | ActionType<typeof setHomePageAction, 'SET_HOMEPAGE'>; + +export interface State { + usersByLogin: { [login: string]: any }; + userLogins: string[]; + currentUser: CurrentUser; +} + +function usersByLogin(state: State['usersByLogin'] = {}, action: Action): State['usersByLogin'] { + if (action.type === 'RECEIVE_CURRENT_USER' && isLoggedIn(action.user)) { + return { ...state, [action.user.login]: action.user }; + } else { + return state; + } +} + +function userLogins(state: State['userLogins'] = [], action: Action): State['userLogins'] { + if (action.type === 'RECEIVE_CURRENT_USER' && isLoggedIn(action.user)) { + return uniq([...state, action.user.login]); + } else { + return state; + } +} + +function currentUser( + state: State['currentUser'] = { isLoggedIn: false }, + action: Action +): State['currentUser'] { + if (action.type === 'RECEIVE_CURRENT_USER') { + return action.user; + } + if (action.type === 'SKIP_ONBOARDING' && isLoggedIn(state)) { + return { ...state, showOnboardingTutorial: false } as LoggedInUser; + } + if (action.type === 'SET_HOMEPAGE' && isLoggedIn(state)) { + return { ...state, homepage: action.homepage } as LoggedInUser; + } + return state; +} + +export default combineReducers({ usersByLogin, userLogins, currentUser }); + +export function getCurrentUser(state: State) { + return state.currentUser; +} + +export function getUserByLogin(state: State, login: string) { + return state.usersByLogin[login]; +} + +export function getUsersByLogins(state: State, logins: string[]) { + return logins.map(login => getUserByLogin(state, login)); +} diff --git a/server/sonar-web/src/main/js/store/users/actions.ts b/server/sonar-web/src/main/js/store/users/actions.ts deleted file mode 100644 index 9c9a146e2a4..00000000000 --- a/server/sonar-web/src/main/js/store/users/actions.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { Dispatch } from 'redux'; -import * as api from '../../api/users'; -import { CurrentUser, HomePage } from '../../app/types'; - -export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER'; -export const SKIP_ONBOARDING = 'SKIP_ONBOARDING'; -export const SET_HOMEPAGE = 'SET_HOMEPAGE'; - -export const receiveCurrentUser = (user: CurrentUser) => ({ - type: RECEIVE_CURRENT_USER, - user -}); - -export const skipOnboarding = () => ({ type: SKIP_ONBOARDING }); - -export const fetchCurrentUser = () => (dispatch: Dispatch<any>) => { - return api.getCurrentUser().then(user => { - dispatch(receiveCurrentUser(user)); - return user; - }); -}; - -export const setHomePage = (homepage: HomePage) => (dispatch: Dispatch<any>) => { - api.setHomePage(homepage).then( - () => { - dispatch({ type: SET_HOMEPAGE, homepage }); - }, - () => {} - ); -}; diff --git a/server/sonar-web/src/main/js/store/users/reducer.ts b/server/sonar-web/src/main/js/store/users/reducer.ts deleted file mode 100644 index a6e79db6d97..00000000000 --- a/server/sonar-web/src/main/js/store/users/reducer.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { combineReducers } from 'redux'; -import { uniq } from 'lodash'; -import { RECEIVE_CURRENT_USER, SKIP_ONBOARDING, SET_HOMEPAGE } from './actions'; -import { CurrentUser } from '../../app/types'; - -interface UsersByLogin { - [login: string]: any; -} - -const usersByLogin = (state: UsersByLogin = {}, action: any = {}) => { - if (action.type === RECEIVE_CURRENT_USER) { - return { ...state, [action.user.login]: action.user }; - } else { - return state; - } -}; - -type UserLogins = string[]; - -const userLogins = (state: UserLogins = [], action: any = {}) => { - if (action.type === RECEIVE_CURRENT_USER) { - return uniq([...state, action.user.login]); - } else { - return state; - } -}; - -const currentUser = (state: CurrentUser | null = null, action: any = {}) => { - if (action.type === RECEIVE_CURRENT_USER) { - return action.user; - } - if (action.type === SKIP_ONBOARDING) { - return state ? { ...state, showOnboardingTutorial: false } : null; - } - if (action.type === SET_HOMEPAGE) { - return state && { ...state, homepage: action.homepage }; - } - return state; -}; - -interface State { - usersByLogin: UsersByLogin; - userLogins: UserLogins; - currentUser: CurrentUser | null; -} - -export default combineReducers({ usersByLogin, userLogins, currentUser }); - -export const getCurrentUser = (state: State) => state.currentUser!; -export const getUserByLogin = (state: State, login: string) => state.usersByLogin[login]; -export const getUsersByLogins = (state: State, logins: string[]) => - logins.map(login => getUserByLogin(state, login)); diff --git a/server/sonar-web/src/main/js/store/languages/actions.js b/server/sonar-web/src/main/js/store/utils/actions.ts index 9b3d1a9d506..4926a67d2a3 100644 --- a/server/sonar-web/src/main/js/store/languages/actions.js +++ b/server/sonar-web/src/main/js/store/utils/actions.ts @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -export const RECEIVE_LANGUAGES = 'RECEIVE_LANGUAGES'; +import { Omit } from '../../app/types'; -export const receiveLanguages = languages => ({ - type: RECEIVE_LANGUAGES, - languages -}); +type ActionCreator = (...args: any[]) => { type: string }; + +export type ActionType<F extends ActionCreator, T> = Omit<ReturnType<F>, 'type'> & { type: T }; diff --git a/server/sonar-web/src/main/js/store/utils/configureStore.js b/server/sonar-web/src/main/js/store/utils/configureStore.ts index 75bb516bfbe..8681815877f 100644 --- a/server/sonar-web/src/main/js/store/utils/configureStore.js +++ b/server/sonar-web/src/main/js/store/utils/configureStore.ts @@ -20,6 +20,9 @@ import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; +type RootReducer = typeof import('../rootReducer').default; +type State = import('../rootReducer').Store; + const middlewares = [thunk]; const composed = []; @@ -27,7 +30,8 @@ if (process.env.NODE_ENV === 'development') { const { createLogger } = require('redux-logger'); middlewares.push(createLogger()); - composed.push(window.devToolsExtension ? window.devToolsExtension() : f => f); + const { devToolsExtension } = window as any; + composed.push(devToolsExtension ? devToolsExtension() : (f: Function) => f); } const finalCreateStore = compose( @@ -35,6 +39,6 @@ const finalCreateStore = compose( ...composed )(createStore); -export default function configureStore(rootReducer, initialState) { +export default function configureStore(rootReducer: RootReducer, initialState?: State) { return finalCreateStore(rootReducer, initialState); } |