diff options
42 files changed, 259 insertions, 93 deletions
diff --git a/server/sonar-web/.flowconfig b/server/sonar-web/.flowconfig new file mode 100644 index 00000000000..8370edc4380 --- /dev/null +++ b/server/sonar-web/.flowconfig @@ -0,0 +1,13 @@ +[ignore] +<PROJECT_ROOT>/node_modules/fbjs.* +<PROJECT_ROOT>/node_modules/react-side-effect.* +<PROJECT_ROOT>/node/.* + +[include] + +[libs] + +[options] +module.file_ext=.js +module.file_ext=.hbs +module.name_mapper.extension='hbs' -> 'empty/object' diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index dfa0f76c71d..f0b6e5829c0 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -42,6 +42,7 @@ "file-loader": "0.9.0", "filesize": "3.3.0", "find-cache-dir": "0.1.1", + "flow-bin": "0.35.0", "fs-extra": "0.30.0", "gzip-size": "3.0.0", "handlebars": "2.0.0", @@ -90,7 +91,8 @@ "build": "node scripts/build.js", "test": "node scripts/test.js", "coverage": "npm test -- --coverage", - "lint": "eslint src/main/js" + "lint": "eslint src/main/js", + "typecheck": "flow check src/main/js" }, "engines": { "node": ">=4" diff --git a/server/sonar-web/src/main/js/api/favorites.js b/server/sonar-web/src/main/js/api/favorites.js index d3b0e2eed44..fe210e0132e 100644 --- a/server/sonar-web/src/main/js/api/favorites.js +++ b/server/sonar-web/src/main/js/api/favorites.js @@ -17,17 +17,18 @@ * 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 { post, requestDelete, getJSON } from '../helpers/request'; export const getFavorites = () => getJSON('/api/favourites'); -export function addFavorite (componentKey) { +export function addFavorite (componentKey: string) { const url = '/api/favourites'; const data = { key: componentKey }; return post(url, data); } -export function removeFavorite (componentKey) { +export function removeFavorite (componentKey: string) { const url = '/api/favourites/' + encodeURIComponent(componentKey); return requestDelete(url); } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/app.js b/server/sonar-web/src/main/js/apps/background-tasks/app.js index c5ed780faa5..849bd897cb7 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/app.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/app.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import ReactDOM from 'react-dom'; import { Router, Route, Redirect, useRouterHistory } from 'react-router'; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js index 11ed4513129..5beee99c9af 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; import debounce from 'lodash/debounce'; @@ -30,6 +31,7 @@ import Search from '../components/Search'; import Tasks from '../components/Tasks'; import { getTypes, getActivity, getStatus, cancelAllTasks, cancelTask as cancelTaskAPI } from '../../../api/ce'; import { updateTask, mapFiltersToParameters } from '../utils'; +import { Task } from '../types'; import '../background-tasks.css'; export default class BackgroundTasksApp extends React.Component { @@ -42,7 +44,7 @@ export default class BackgroundTasksApp extends React.Component { location: RouterPropTypes.location.isRequired }; - state = { + state: any = { loading: true, tasks: [], @@ -67,11 +69,11 @@ export default class BackgroundTasksApp extends React.Component { }); } - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: any, nextState: any) { return shallowCompare(this, nextProps, nextState); } - componentDidUpdate (prevProps) { + componentDidUpdate (prevProps: any) { if (prevProps.component !== this.props.component || prevProps.location !== this.props.location) { this.loadTasksDebounced(); @@ -82,6 +84,9 @@ export default class BackgroundTasksApp extends React.Component { this.mounted = false; } + loadTasksDebounced: any; + mounted: boolean; + loadTasks () { this.setState({ loading: true }); @@ -93,7 +98,7 @@ export default class BackgroundTasksApp extends React.Component { const query = this.props.location.query.query || DEFAULT_FILTERS.query; const filters = { status, taskType, currents, minSubmittedAt, maxExecutedAt, query }; - const parameters = mapFiltersToParameters(filters); + const parameters: any = mapFiltersToParameters(filters); if (this.props.component) { parameters.componentId = this.props.component.id; @@ -120,7 +125,7 @@ export default class BackgroundTasksApp extends React.Component { }); } - handleFilterUpdate (nextState) { + handleFilterUpdate (nextState: any) { const nextQuery = { ...this.props.location.query, ...nextState }; // remove defaults @@ -136,7 +141,7 @@ export default class BackgroundTasksApp extends React.Component { }); } - handleCancelTask (task) { + handleCancelTask (task: Task) { this.setState({ loading: true }); cancelTaskAPI(task.id).then(nextTask => { @@ -147,7 +152,7 @@ export default class BackgroundTasksApp extends React.Component { }); } - handleFilterTask (task) { + handleFilterTask (task: Task) { this.handleFilterUpdate({ query: task.componentKey }); } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js index 855646a4966..f6cdd97a28b 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js @@ -17,12 +17,13 @@ * 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 React from 'react'; import Checkbox from '../../../components/controls/Checkbox'; import { CURRENTS } from '../constants'; -const CurrentsFilter = ({ value, onChange }) => { +const CurrentsFilter = ({ value, onChange } : { value: ?string, onChange: any }) => { function handleChange (value) { const newValue = value ? CURRENTS.ONLY_CURRENTS : CURRENTS.ALL; onChange(newValue); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js index ace8d6e428d..bdebed1fcaf 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js @@ -17,6 +17,7 @@ * 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 $ from 'jquery'; import moment from 'moment'; import React, { Component } from 'react'; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Footer.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Footer.js index b9fd65ca96e..60f2f5be872 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Footer.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Footer.js @@ -17,13 +17,14 @@ * 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 React from 'react'; - +import { Task } from '../types'; import { translateWithParameters } from '../../../helpers/l10n'; const LIMIT = 1000; -const Footer = ({ tasks }) => { +const Footer = ({ tasks }: { tasks: Task[] }) => { if (tasks.length < LIMIT) { return null; } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js index 8cc1f251797..a8dc2607fde 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import { translate } from '../../../helpers/l10n'; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js index 9dd234a6207..1b03a3bbead 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import StatusFilter from './StatusFilter'; @@ -37,32 +38,32 @@ export default class Search extends React.Component { onReload: React.PropTypes.func.isRequired }; - handleStatusChange (status) { + handleStatusChange (status: string) { this.props.onFilterUpdate({ status }); } - handleTypeChange (taskType) { + handleTypeChange (taskType: string) { this.props.onFilterUpdate({ taskType }); } - handleCurrentsChange (currents) { + handleCurrentsChange (currents: string) { this.props.onFilterUpdate({ currents }); } - handleDateChange (date) { + handleDateChange (date: string) { this.props.onFilterUpdate(date); } - handleQueryChange (query) { + handleQueryChange (query: string) { this.props.onFilterUpdate({ query }); } - handleReload (e) { + handleReload (e: any) { e.target.blur(); this.props.onReload(); } - handleReset (e) { + handleReset (e: any) { e.preventDefault(); e.target.blur(); this.props.onFilterUpdate(DEFAULT_FILTERS); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.js index 46da2cbc416..a7f6ffe3e29 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; @@ -30,17 +31,17 @@ export default class Stats extends React.Component { onCancelAllPending: React.PropTypes.func.isRequired }; - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: any, nextState: any) { return shallowCompare(this, nextProps, nextState); } - handleCancelAllPending (e) { + handleCancelAllPending (e: any) { e.preventDefault(); e.target.blur(); this.props.onCancelAllPending(); } - handleShowFailing (e) { + handleShowFailing (e: any) { e.preventDefault(); e.target.blur(); this.props.onShowFailing(); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js index 5228ad42f16..f0693466bd6 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js @@ -17,13 +17,14 @@ * 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 React from 'react'; import Select from 'react-select'; import { STATUSES } from '../constants'; import { translate } from '../../../helpers/l10n'; -const StatusFilter = ({ value, onChange }) => { +const StatusFilter = ({ value, onChange }: { value: ?string, onChange: any }) => { const options = [ { value: STATUSES.ALL, label: translate('background_task.status.ALL') }, { value: STATUSES.ALL_EXCEPT_PENDING, label: translate('background_task.status.ALL_EXCEPT_PENDING') }, diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js index ddced30d774..cdbf6650c7e 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js @@ -17,13 +17,15 @@ * 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 React from 'react'; import TaskType from './TaskType'; import { getComponentUrl } from '../../../helpers/urls'; import QualifierIcon from '../../../components/shared/qualifier-icon'; +import { Task } from '../types'; -const TaskComponent = ({ task, types }) => { +const TaskComponent = ({ task, types }: { task: Task, types: string[] }) => { if (!task.componentKey) { return ( <td> diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js index 581f5250d18..c1ff060a676 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js @@ -17,10 +17,11 @@ * 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 moment from 'moment'; import React from 'react'; -const TaskDate = ({ date, baseDate, format }) => { +const TaskDate = ({ date, baseDate, format }: { date: string, baseDate: string, format: string }) => { const m = moment(date); const baseM = moment(baseDate); const diff = (date && baseDate) ? m.diff(baseM, 'days') : 0; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js index 60972ebf86a..c7275f1668e 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js @@ -17,14 +17,16 @@ * 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 moment from 'moment'; import React from 'react'; +import { Task } from '../types'; function isAnotherDay (a, b) { return !moment(a).isSame(moment(b), 'day'); } -const TaskDay = ({ task, prevTask }) => { +const TaskDay = ({ task, prevTask } : { task: Task, prevTask: ?Task }) => { const shouldDisplay = !prevTask || isAnotherDay(task.submittedAt, prevTask.submittedAt); return ( diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js index 98173c1af04..e0d12d09b7a 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js @@ -17,10 +17,12 @@ * 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 React from 'react'; import { formatDuration } from '../utils'; +import { Task } from '../types'; -const TaskExecutionTime = ({ task }) => { +const TaskExecutionTime = ({ task } : { task: Task }) => { return ( <td className="thin nowrap text-right"> {formatDuration(task.executionTimeMs)} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js index 69e4ad97cf1..89e925590fe 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js @@ -17,13 +17,15 @@ * 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 React from 'react'; import { STATUSES } from './../constants'; import PendingIcon from '../../../components/shared/pending-icon'; import { translate } from '../../../helpers/l10n'; +import { Task } from '../types'; -const TaskStatus = ({ task }) => { +const TaskStatus = ({ task }: { task: Task }) => { let inner; switch (task.status) { diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.js index ed10f60f62b..0174bdbe0b0 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.js @@ -17,11 +17,12 @@ * 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 React from 'react'; - +import { Task } from '../types'; import { translate } from '../../../helpers/l10n'; -const TaskType = ({ task }) => { +const TaskType = ({ task }: { task: Task }) => { return ( <span className="note nowrap spacer-left"> {'['} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.js index d6a9633354f..d696d47f9a2 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; import classNames from 'classnames'; @@ -34,7 +35,7 @@ export default class Tasks extends React.Component { onFilterTask: React.PropTypes.func.isRequired }; - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: any, nextState: any) { return shallowCompare(this, nextProps, nextState); } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js index 825ba90fb6a..d167e23a0f6 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js @@ -17,13 +17,14 @@ * 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 React from 'react'; import Select from 'react-select'; import { ALL_TYPES } from '../constants'; import { translate } from '../../../helpers/l10n'; -const TypesFilter = ({ value, onChange, types }) => { +const TypesFilter = ({ value, onChange, types }: { value: string, onChange: any, types: string[] }) => { const options = types.map(t => { return { value: t, diff --git a/server/sonar-web/src/main/js/apps/background-tasks/constants.js b/server/sonar-web/src/main/js/apps/background-tasks/constants.js index ed5f571d2a8..813f359ba32 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/constants.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/constants.js @@ -17,6 +17,7 @@ * 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 const STATUSES = { ALL: '__ALL__', ALL_EXCEPT_PENDING: '__ALL_EXCEPT_PENDING__', diff --git a/server/sonar-web/src/main/js/apps/background-tasks/types.js b/server/sonar-web/src/main/js/apps/background-tasks/types.js new file mode 100644 index 00000000000..00c000fd0c8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/background-tasks/types.js @@ -0,0 +1,22 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +export type Task = { + id: string; +}; diff --git a/server/sonar-web/src/main/js/apps/background-tasks/utils.js b/server/sonar-web/src/main/js/apps/background-tasks/utils.js index 97207fa9864..cf0c4c89b8b 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/utils.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/utils.js @@ -17,13 +17,15 @@ * 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 { STATUSES, ALL_TYPES, CURRENTS } from './constants'; +import { Task } from './types'; -export function updateTask (tasks, newTask) { +export function updateTask (tasks: Task[], newTask: Task) { return tasks.map(task => task.id === newTask.id ? newTask : task); } -export function mapFiltersToParameters (filters = {}) { +export function mapFiltersToParameters (filters: any = {}) { const parameters = {}; if (filters.status === STATUSES.ALL) { @@ -79,7 +81,7 @@ function format (int, suffix) { return `${int}${suffix}`; } -export function formatDuration (value) { +export function formatDuration (value: ?number) { if (!value) { return ''; } diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js index 44d16ad6bdc..285f9e769e7 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js +++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import { connect } from 'react-redux'; import CategoriesList from './CategoriesList'; diff --git a/server/sonar-web/src/main/js/apps/settings/components/App.js b/server/sonar-web/src/main/js/apps/settings/components/App.js index c80dcbfc680..9f2c99ecb6b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/App.js +++ b/server/sonar-web/src/main/js/apps/settings/components/App.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; import { connect } from 'react-redux'; @@ -29,14 +30,20 @@ import { fetchSettings } from '../store/actions'; import { getDefaultCategory } from '../store/rootReducer'; import '../styles.css'; -class App extends React.Component { - static propTypes = { - component: React.PropTypes.object, - fetchSettings: React.PropTypes.func.isRequired, - defaultCategory: React.PropTypes.string - }; +type Props = { + component: { key: string }, + defaultCategory: ?string, + fetchSettings(componentKey: ?string): Promise<any>, + location: { query: {} } +}; + +type State = { + loaded: boolean +}; - state = { loaded: false }; +class App extends React.Component { + props: Props; + state: State = { loaded: false }; componentDidMount () { document.querySelector('html').classList.add('dashboard-page'); @@ -46,7 +53,7 @@ class App extends React.Component { }); } - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: Props, nextState: ?{}) { return shallowCompare(this, nextProps, nextState); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js index aa833d5ce90..5fb235d1da6 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js +++ b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js @@ -17,24 +17,33 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; import sortBy from 'lodash/sortBy'; import { IndexLink } from 'react-router'; import { getCategoryName } from '../utils'; +type Category = { + key: string, + name: string +}; + +type Props = { + categories: Category[], + component?: { key: string }, + defaultCategory: string, + selectedCategory: string +}; + export default class CategoriesList extends React.Component { - static propTypes = { - categories: React.PropTypes.array.isRequired, - selectedCategory: React.PropTypes.string.isRequired, - defaultCategory: React.PropTypes.string.isRequired - }; + props: Props; - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: Props, nextState: ?{}) { return shallowCompare(this, nextProps, nextState); } - renderLink (category) { + renderLink (category: Category) { const query = {}; if (category.key !== this.props.defaultCategory) { diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js index cc2e6d8c24e..8d7bf30fc1d 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js +++ b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js @@ -17,11 +17,13 @@ * 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 React from 'react'; import { connect } from 'react-redux'; import SubCategoryDefinitionsList from './SubCategoryDefinitionsList'; import { getSettingsForCategory } from '../store/rootReducer'; + class CategoryDefinitionsList extends React.Component { render () { return <SubCategoryDefinitionsList {...this.props}/>; diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.js b/server/sonar-web/src/main/js/apps/settings/components/Definition.js index 76202b6005f..065b6b447f4 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/Definition.js +++ b/server/sonar-web/src/main/js/apps/settings/components/Definition.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import { connect } from 'react-redux'; import shallowCompare from 'react-addons-shallow-compare'; @@ -33,6 +34,9 @@ import { cancelChange, changeValue } from '../store/settingsPage/changedValues/a import { TYPE_PASSWORD } from '../constants'; class Definition extends React.Component { + mounted: boolean; + timeout: number; + static propTypes = { component: React.PropTypes.object, setting: React.PropTypes.object.isRequired, diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js index edbeafecf9e..97d48d20d8b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; import { translate } from '../../../helpers/l10n'; @@ -27,17 +28,17 @@ export default class DefinitionChanges extends React.Component { onCancel: React.PropTypes.func.isRequired }; - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: {}, nextState: ?{}) { return shallowCompare(this, nextProps, nextState); } - handleSaveClick (e) { + handleSaveClick (e: any) { e.preventDefault(); e.target.blur(); this.props.onSave(); } - handleCancelChange (e) { + handleCancelChange (e: any) { e.preventDefault(); e.target.blur(); this.props.onCancel(); diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js index 452b24c5ea5..9d561ac6174 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import { getSettingValue, isEmptyValue, getDefaultValue } from '../utils'; import { translate } from '../../../helpers/l10n'; @@ -28,7 +29,7 @@ export default class DefinitionDefaults extends React.Component { onReset: React.PropTypes.func.isRequired }; - handleReset (e) { + handleReset (e: any) { e.preventDefault(); e.target.blur(); this.props.onReset(); diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js index 77d0d7fa95b..bbc73eed15f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; import Definition from './Definition'; @@ -27,7 +28,7 @@ export default class DefinitionsList extends React.Component { settings: React.PropTypes.array.isRequired }; - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: {}, nextState: ?{}) { return shallowCompare(this, nextProps, nextState); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js b/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js index f64ef91664b..da821b7d65f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js +++ b/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js @@ -17,6 +17,7 @@ * 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 { connect } from 'react-redux'; import GlobalMessages from '../../../components/controls/GlobalMessages'; import { getGlobalMessages } from '../store/rootReducer'; diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js index 34e402122cb..f44843a0fde 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import { translate } from '../../../helpers/l10n'; diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js index 3bb2ceefa46..ab8ed689082 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js +++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js @@ -17,6 +17,7 @@ * 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 React from 'react'; import shallowCompare from 'react-addons-shallow-compare'; import groupBy from 'lodash/groupBy'; @@ -31,11 +32,11 @@ export default class SubCategoryDefinitionsList extends React.Component { settings: React.PropTypes.array.isRequired }; - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate (nextProps: {}, nextState: ?{}) { return shallowCompare(this, nextProps, nextState); } - renderEmailForm (subCategoryKey) { + renderEmailForm (subCategoryKey: string) { const isEmailSettings = this.props.category === 'general' && subCategoryKey === 'email'; if (!isEmailSettings) { return null; diff --git a/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js b/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js index 589a05df223..e3c360f093e 100644 --- a/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js +++ b/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js @@ -17,14 +17,17 @@ * 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_DEFINITIONS = 'RECEIVE_DEFINITIONS'; +// @flow +import type { Definition } from '../../types'; + +export const RECEIVE_DEFINITIONS: string = 'RECEIVE_DEFINITIONS'; /** * Receive definitions action creator * @param {Array} definitions * @returns {Object} */ -export const receiveDefinitions = definitions => ({ +export const receiveDefinitions = (definitions: Definition[]) => ({ type: RECEIVE_DEFINITIONS, definitions }); diff --git a/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js index d80ffc47c09..eb788853ea4 100644 --- a/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js +++ b/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js @@ -17,13 +17,19 @@ * 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 keyBy from 'lodash/keyBy'; import sortBy from 'lodash/sortBy'; import uniqBy from 'lodash/uniqBy'; import { RECEIVE_DEFINITIONS } from './actions'; import { DEFAULT_CATEGORY, getCategoryName } from '../../utils'; +import type { Definition } from '../../types'; -const reducer = (state = {}, action = {}) => { +type State = { [key: string]: Definition }; + +type Action = { type: string, definitions: Definition[] }; + +const reducer = (state: State = {}, action: Action) => { if (action.type === RECEIVE_DEFINITIONS) { const definitionsByKey = keyBy(action.definitions, 'key'); return { ...state, ...definitionsByKey }; @@ -34,18 +40,20 @@ const reducer = (state = {}, action = {}) => { export default reducer; -export const getDefinition = (state, key) => state[key]; +export const getDefinition = (state: State, key: string): Definition => + state[key]; -export const getAllDefinitions = state => Object.values(state); +export const getAllDefinitions = (state: State): Definition[] => + Object.keys(state).map(key => state[key]); -export const getDefinitionsForCategory = (state, category) => +export const getDefinitionsForCategory = (state: State, category: string) => getAllDefinitions(state).filter(definition => definition.category.toLowerCase() === category.toLowerCase()); -export const getAllCategories = state => uniqBy( +export const getAllCategories = (state: State) => uniqBy( getAllDefinitions(state).map(definition => definition.category), category => category.toLowerCase()); -export const getDefaultCategory = state => { +export const getDefaultCategory = (state: State) => { const categories = getAllCategories(state); if (categories.includes(DEFAULT_CATEGORY)) { return DEFAULT_CATEGORY; diff --git a/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js b/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js index 0c3b7dfe214..90658fcb3db 100644 --- a/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js +++ b/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js @@ -17,6 +17,7 @@ * 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 definitions, * as fromDefinitions from './definitions/reducer'; import values, * as fromValues from './values/reducer'; @@ -25,6 +26,15 @@ import licenses, * as fromLicenses from './licenses/reducer'; import globalMessages, * as fromGlobalMessages from '../../../components/store/globalMessages'; import encryptionPage from './encryptionPage/reducer'; +type State = { + definitions: {}, + encryptionPage: {}, + globalMessages: {}, + licenses: {}, + settingsPage: {}, + values: {} +}; + const rootReducer = combineReducers({ definitions, values, @@ -36,30 +46,41 @@ const rootReducer = combineReducers({ export default rootReducer; -export const getDefinition = (state, key) => fromDefinitions.getDefinition(state.definitions, key); +export const getDefinition = (state: State, key: string) => + fromDefinitions.getDefinition(state.definitions, key); -export const getAllCategories = state => fromDefinitions.getAllCategories(state.definitions); +export const getAllCategories = (state: State) => + fromDefinitions.getAllCategories(state.definitions); -export const getDefaultCategory = state => fromDefinitions.getDefaultCategory(state.definitions); +export const getDefaultCategory = (state: State) => + fromDefinitions.getDefaultCategory(state.definitions); -export const getValue = (state, key) => fromValues.getValue(state.values, key); +export const getValue = (state: State, key: string) => + fromValues.getValue(state.values, key); -export const getSettingsForCategory = (state, category) => +export const getSettingsForCategory = (state: State, category: string) => fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => ({ ...getValue(state, definition.key), definition })); -export const getChangedValue = (state, key) => fromSettingsPage.getChangedValue(state.settingsPage, key); +export const getChangedValue = (state: State, key: string) => + fromSettingsPage.getChangedValue(state.settingsPage, key); -export const isLoading = (state, key) => fromSettingsPage.isLoading(state.settingsPage, key); +export const isLoading = (state: State, key: string) => + fromSettingsPage.isLoading(state.settingsPage, key); -export const getLicenseByKey = (state, key) => fromLicenses.getLicenseByKey(state.licenses, key); +export const getLicenseByKey = (state: State, key: string) => + fromLicenses.getLicenseByKey(state.licenses, key); -export const getAllLicenseKeys = state => fromLicenses.getAllLicenseKeys(state.licenses); +export const getAllLicenseKeys = (state: State) => + fromLicenses.getAllLicenseKeys(state.licenses); -export const getValidationMessage = (state, key) => fromSettingsPage.getValidationMessage(state.settingsPage, key); +export const getValidationMessage = (state: State, key: string) => + fromSettingsPage.getValidationMessage(state.settingsPage, key); -export const getEncryptionState = state => state.encryptionPage; +export const getEncryptionState = (state: State) => + state.encryptionPage; -export const getGlobalMessages = state => fromGlobalMessages.getGlobalMessages(state.globalMessages); +export const getGlobalMessages = (state: State) => + fromGlobalMessages.getGlobalMessages(state.globalMessages); diff --git a/server/sonar-web/src/main/js/apps/settings/store/values/actions.js b/server/sonar-web/src/main/js/apps/settings/store/values/actions.js index 582b6078233..e088215d7fd 100644 --- a/server/sonar-web/src/main/js/apps/settings/store/values/actions.js +++ b/server/sonar-web/src/main/js/apps/settings/store/values/actions.js @@ -17,14 +17,16 @@ * 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_VALUES = 'RECEIVE_VALUES'; +// @flow +import type { SettingValue } from '../../types'; +export const RECEIVE_VALUES: string = 'RECEIVE_VALUES'; /** * Receive settings action creator * @param {Array} settings * @returns {Object} */ -export const receiveValues = settings => ({ +export const receiveValues = (settings: SettingValue[]) => ({ type: RECEIVE_VALUES, settings }); diff --git a/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js index 5ed6709bc5d..2369b75f3de 100644 --- a/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js +++ b/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js @@ -17,10 +17,16 @@ * 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 keyBy from 'lodash/keyBy'; import { RECEIVE_VALUES } from './actions'; +import type { SettingValue } from '../../types'; -const reducer = (state = {}, action = {}) => { +type State = { [key: string]: {} }; + +type Action = { type: string, settings: SettingValue[] }; + +const reducer = (state: State = {}, action: Action) => { if (action.type === RECEIVE_VALUES) { const settingsByKey = keyBy(action.settings, 'key'); return { ...state, ...settingsByKey }; @@ -31,4 +37,4 @@ const reducer = (state = {}, action = {}) => { export default reducer; -export const getValue = (state, key) => state[key]; +export const getValue = (state: State, key: string) => state[key]; diff --git a/server/sonar-web/src/main/js/apps/settings/types.js b/server/sonar-web/src/main/js/apps/settings/types.js new file mode 100644 index 00000000000..f82ae12a43b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/types.js @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 Definition = { + key: string, + category: string +}; + +export type SettingValue = { + value?: string +}; diff --git a/server/sonar-web/src/main/js/components/source-viewer/header.js b/server/sonar-web/src/main/js/components/source-viewer/header.js index 86b0df62980..5e3ed9e866b 100644 --- a/server/sonar-web/src/main/js/components/source-viewer/header.js +++ b/server/sonar-web/src/main/js/components/source-viewer/header.js @@ -17,6 +17,7 @@ * 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 $ from 'jquery'; import _ from 'underscore'; import Marionette from 'backbone.marionette'; @@ -24,7 +25,7 @@ import MoreActionsView from './more-actions'; import MeasuresOverlay from './measures-overlay'; import Template from './templates/source-viewer-header.hbs'; -const API_FAVORITE = window.baseUrl + '/api/favourites'; +const API_FAVORITE: string = window.baseUrl + '/api/favourites'; export default Marionette.ItemView.extend({ template: Template, @@ -96,4 +97,3 @@ export default Marionette.ItemView.extend({ }); } }); - diff --git a/server/sonar-web/src/main/js/helpers/l10n.js b/server/sonar-web/src/main/js/helpers/l10n.js index 425dd32674f..6b8f85ef4fc 100644 --- a/server/sonar-web/src/main/js/helpers/l10n.js +++ b/server/sonar-web/src/main/js/helpers/l10n.js @@ -17,26 +17,29 @@ * 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 moment from 'moment'; import { request } from './request'; let messages = {}; -export function translate (...keys) { +export function translate (...keys: string[]) { const messageKey = keys.join('.'); return messages[messageKey] || messageKey; } -export function translateWithParameters (messageKey, ...parameters) { +export function translateWithParameters (messageKey: string, ...parameters: Array<string|number>) { const message = messages[messageKey]; if (message) { - return parameters.reduce((acc, parameter, index) => acc.replace(`{${index}}`, parameter), message); + return parameters + .map(parameter => String(parameter)) + .reduce((acc, parameter, index) => acc.replace(`{${index}}`, parameter), message); } else { return `${messageKey}.${parameters.join('.')}`; } } -export function hasMessage (...keys) { +export function hasMessage (...keys: string[]) { const messageKey = keys.join('.'); return messages[messageKey] != null; } @@ -56,7 +59,7 @@ function makeRequest (params) { case 200: return response.json(); case 304: - return JSON.parse(localStorage.getItem('l10n.bundle')); + return JSON.parse(localStorage.getItem('l10n.bundle') || '{}'); case 401: window.location = window.baseUrl + '/sessions/new?return_to=' + encodeURIComponent(window.location.pathname + window.location.search + window.location.hash); @@ -113,7 +116,7 @@ export function requestMessages () { }); } -export function resetBundle (bundle) { +export function resetBundle (bundle: any) { messages = bundle; } @@ -123,19 +126,19 @@ export function installGlobal () { window.requestMessages = requestMessages; } -export function getLocalizedDashboardName (baseName) { +export function getLocalizedDashboardName (baseName: string) { const l10nKey = `dashboard.${baseName}.name`; const l10nLabel = translate(l10nKey); return l10nLabel !== l10nKey ? l10nLabel : baseName; } -export function getLocalizedMetricName (metric) { +export function getLocalizedMetricName (metric: { key: string, name: string }) { const bundleKey = `metric.${metric.key}.name`; const fromBundle = translate(bundleKey); return fromBundle !== bundleKey ? fromBundle : metric.name; } -export function getLocalizedMetricDomain (domainName) { +export function getLocalizedMetricDomain (domainName: string) { const bundleKey = `metric_domain.${domainName}`; const fromBundle = translate(bundleKey); return fromBundle !== bundleKey ? fromBundle : domainName; |