Переглянути джерело

SONAR-15992 Remove Global Settings from redux

tags/9.4.0.54424
Jeremy Davis 2 роки тому
джерело
коміт
accf42f839
100 змінених файлів з 784 додано та 718 видалено
  1. 2
    1
      server/sonar-web/src/main/js/app/components/AdminContainer.tsx
  2. 18
    16
      server/sonar-web/src/main/js/app/components/App.tsx
  3. 2
    1
      server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
  4. 1
    1
      server/sonar-web/src/main/js/app/components/GlobalFooter.tsx
  5. 6
    27
      server/sonar-web/src/main/js/app/components/PageTracker.tsx
  6. 2
    1
      server/sonar-web/src/main/js/app/components/StartupModal.tsx
  7. 20
    24
      server/sonar-web/src/main/js/app/components/__tests__/App-test.tsx
  8. 0
    19
      server/sonar-web/src/main/js/app/components/__tests__/PageTracker-test.tsx
  9. 1
    1
      server/sonar-web/src/main/js/app/components/app-state/AppStateContext.tsx
  10. 2
    15
      server/sonar-web/src/main/js/app/components/app-state/AppStateContextProvider.tsx
  11. 6
    8
      server/sonar-web/src/main/js/app/components/app-state/__tests__/AppStateContextProvider-test.tsx
  12. 19
    0
      server/sonar-web/src/main/js/app/components/app-state/__tests__/__snapshots__/AppStateContextProvider-test.tsx.snap
  13. 1
    1
      server/sonar-web/src/main/js/app/components/app-state/__tests__/withAppStateContext-test.tsx
  14. 1
    1
      server/sonar-web/src/main/js/app/components/app-state/withAppStateContext.tsx
  15. 2
    1
      server/sonar-web/src/main/js/app/components/extensions/Extension.tsx
  16. 1
    1
      server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx
  17. 2
    0
      server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
  18. 1
    1
      server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx
  19. 1
    1
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx
  20. 2
    1
      server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
  21. 2
    1
      server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx
  22. 10
    16
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx
  23. 2
    1
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
  24. 50
    0
      server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavBranding-test.tsx
  25. 2
    2
      server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNav-test.tsx.snap
  26. 52
    0
      server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap
  27. 1
    1
      server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap
  28. 2
    1
      server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx
  29. 1
    1
      server/sonar-web/src/main/js/app/index.ts
  30. 2
    1
      server/sonar-web/src/main/js/app/utils/startReactApp.tsx
  31. 28
    40
      server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx
  32. 32
    10
      server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx
  33. 1
    1
      server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx
  34. 1
    1
      server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx
  35. 2
    1
      server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
  36. 1
    1
      server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
  37. 2
    1
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
  38. 1
    1
      server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap
  39. 11
    16
      server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
  40. 13
    15
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-test.tsx
  41. 4
    4
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.tsx.snap
  42. 11
    28
      server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx
  43. 0
    52
      server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx
  44. 48
    0
      server/sonar-web/src/main/js/apps/marketplace/__tests__/MarketplaceAppContainer-test.tsx
  45. 54
    0
      server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/MarketplaceAppContainer-test.tsx.snap
  46. 1
    1
      server/sonar-web/src/main/js/apps/marketplace/routes.ts
  47. 2
    1
      server/sonar-web/src/main/js/apps/overview/components/App.tsx
  48. 3
    3
      server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx
  49. 36
    6
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap
  50. 2
    1
      server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
  51. 2
    1
      server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx
  52. 1
    1
      server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap
  53. 1
    1
      server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
  54. 1
    1
      server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx
  55. 2
    1
      server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx
  56. 2
    1
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
  57. 2
    1
      server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx
  58. 2
    1
      server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
  59. 2
    7
      server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
  60. 1
    1
      server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap
  61. 1
    1
      server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap
  62. 1
    1
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsUser-test.tsx.snap
  63. 13
    13
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap
  64. 2
    2
      server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/__snapshots__/AssigneeSelectionRenderer-test.tsx.snap
  65. 2
    1
      server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
  66. 2
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
  67. 1
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/CreationTooltip.tsx
  68. 2
    1
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
  69. 0
    55
      server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts
  70. 0
    39
      server/sonar-web/src/main/js/apps/settings/store/actions.ts
  71. 0
    78
      server/sonar-web/src/main/js/apps/settings/store/values.ts
  72. 1
    1
      server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
  73. 2
    2
      server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserListItem-test.tsx.snap
  74. 4
    4
      server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.tsx.snap
  75. 2
    1
      server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx
  76. 1
    1
      server/sonar-web/src/main/js/components/docs/DocLink.tsx
  77. 3
    3
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
  78. 4
    4
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.tsx.snap
  79. 2
    2
      server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/ChangelogPopup-test.tsx.snap
  80. 1
    1
      server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetAssigneePopup-test.tsx.snap
  81. 2
    2
      server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SimilarIssuesPopup-test.tsx.snap
  82. 3
    2
      server/sonar-web/src/main/js/components/measure/Measure.tsx
  83. 94
    0
      server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx
  84. 1
    13
      server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx
  85. 62
    0
      server/sonar-web/src/main/js/components/measure/__tests__/RatingTooltipContent-test.tsx
  86. 7
    8
      server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap
  87. 37
    0
      server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/RatingTooltipContent-test.tsx.snap
  88. 1
    10
      server/sonar-web/src/main/js/components/measure/utils.ts
  89. 1
    1
      server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx
  90. 2
    1
      server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/AnalysisCommand.tsx
  91. 1
    1
      server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx
  92. 2
    1
      server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx
  93. 1
    1
      server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx
  94. 2
    1
      server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx
  95. 29
    27
      server/sonar-web/src/main/js/components/ui/Avatar.tsx
  96. 11
    5
      server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.tsx
  97. 1
    1
      server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx
  98. 0
    26
      server/sonar-web/src/main/js/helpers/analytics.js
  99. 0
    62
      server/sonar-web/src/main/js/helpers/measures.ts
  100. 0
    0
      server/sonar-web/src/main/js/helpers/testMocks.ts

+ 2
- 1
server/sonar-web/src/main/js/app/components/AdminContainer.tsx Переглянути файл

@@ -24,8 +24,9 @@ import { getPendingPlugins } from '../../api/plugins';
import { getSystemStatus, waitSystemUPStatus } from '../../api/system';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { PendingPluginResult } from '../../types/plugins';
import { AppState, Extension, SysStatus } from '../../types/types';
import { Extension, SysStatus } from '../../types/types';
import AdminContext, { defaultPendingPlugins, defaultSystemStatus } from './AdminContext';
import withAppStateContext from './app-state/withAppStateContext';
import SettingsNav from './nav/settings/SettingsNav';

+ 18
- 16
server/sonar-web/src/main/js/app/components/App.tsx Переглянути файл

@@ -18,16 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { connect } from 'react-redux';
import { lazyLoadComponent } from '../../components/lazyLoadComponent';
import { getGlobalSettingValue, Store } from '../../store/rootReducer';
import { AppState } from '../../types/appstate';
import { GlobalSettingKeys } from '../../types/settings';
import withAppStateContext from './app-state/withAppStateContext';
import KeyboardShortcutsModal from './KeyboardShortcutsModal';

const PageTracker = lazyLoadComponent(() => import('./PageTracker'));

interface Props {
enableGravatar: boolean;
gravatarServerUrl: string;
appState: AppState;
}

export class App extends React.PureComponent<Props> {
@@ -68,8 +68,19 @@ export class App extends React.PureComponent<Props> {
};

renderPreconnectLink = () => {
const {
appState: { settings }
} = this.props;

const enableGravatar = settings[GlobalSettingKeys.EnableGravatar] === 'true';
const gravatarServerUrl = settings[GlobalSettingKeys.GravatarServerUrl];

if (!enableGravatar || !gravatarServerUrl) {
return null;
}

const parser = document.createElement('a');
parser.href = this.props.gravatarServerUrl;
parser.href = gravatarServerUrl;
if (parser.hostname !== window.location.hostname) {
return <link href={parser.origin} rel="preconnect" />;
}
@@ -79,7 +90,7 @@ export class App extends React.PureComponent<Props> {
render() {
return (
<>
<PageTracker>{this.props.enableGravatar && this.renderPreconnectLink()}</PageTracker>
<PageTracker>{this.renderPreconnectLink()}</PageTracker>
{this.props.children}
<KeyboardShortcutsModal />
</>
@@ -87,13 +98,4 @@ export class App extends React.PureComponent<Props> {
}
}

const mapStateToProps = (state: Store) => {
const enableGravatar = getGlobalSettingValue(state, 'sonar.lf.enableGravatar');
const gravatarServerUrl = getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl');
return {
enableGravatar: Boolean(enableGravatar && enableGravatar.value === 'true'),
gravatarServerUrl: (gravatarServerUrl && gravatarServerUrl.value) || ''
};
};

export default connect(mapStateToProps)(App);
export default withAppStateContext(App);

+ 2
- 1
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx Переглянути файл

@@ -39,10 +39,11 @@ import {
ProjectAlmBindingConfigurationErrors,
ProjectAlmBindingResponse
} from '../../types/alm-settings';
import { AppState } from '../../types/appstate';
import { BranchLike } from '../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../types/component';
import { Task, TaskStatuses, TaskTypes, TaskWarning } from '../../types/tasks';
import { AppState, Component, Status } from '../../types/types';
import { Component, Status } from '../../types/types';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
import withAppStateContext from './app-state/withAppStateContext';
import ComponentContainerNotFound from './ComponentContainerNotFound';

+ 1
- 1
server/sonar-web/src/main/js/app/components/GlobalFooter.tsx Переглянути файл

@@ -23,8 +23,8 @@ import InstanceMessage from '../../components/common/InstanceMessage';
import { Alert } from '../../components/ui/Alert';
import { getEdition } from '../../helpers/editions';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { EditionKey } from '../../types/editions';
import { AppState } from '../../types/types';
import withAppStateContext from './app-state/withAppStateContext';
import GlobalFooterBranding from './GlobalFooterBranding';


+ 6
- 27
server/sonar-web/src/main/js/app/components/PageTracker.tsx Переглянути файл

@@ -19,19 +19,15 @@
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { connect } from 'react-redux';
import { Location, withRouter } from '../../components/hoc/withRouter';
import { gtm } from '../../helpers/analytics';
import { installScript } from '../../helpers/extensions';
import { getWebAnalyticsPageHandlerFromCache } from '../../helpers/extensionsHandler';
import { getInstance } from '../../helpers/system';
import { getGlobalSettingValue, Store } from '../../store/rootReducer';
import { AppState } from '../../types/types';
import { AppState } from '../../types/appstate';
import withAppStateContext from './app-state/withAppStateContext';

interface Props {
location: Location;
trackingIdGTM?: string;
appState: AppState;
}

@@ -43,54 +39,37 @@ export class PageTracker extends React.Component<Props, State> {
state: State = {};

componentDidMount() {
const { trackingIdGTM, appState } = this.props;
const { appState } = this.props;

if (appState.webAnalyticsJsPath && !getWebAnalyticsPageHandlerFromCache()) {
installScript(appState.webAnalyticsJsPath, 'head');
}

if (trackingIdGTM) {
gtm(trackingIdGTM);
}
}

trackPage = () => {
const { location, trackingIdGTM } = this.props;
const { location } = this.props;
const { lastLocation } = this.state;
const { dataLayer } = window as any;
const locationChanged = location.pathname !== lastLocation;
const webAnalyticsPageChange = getWebAnalyticsPageHandlerFromCache();

if (webAnalyticsPageChange && locationChanged) {
this.setState({ lastLocation: location.pathname });
setTimeout(() => webAnalyticsPageChange(location.pathname), 500);
} else if (dataLayer && dataLayer.push && trackingIdGTM && location.pathname !== '/') {
this.setState({ lastLocation: location.pathname });
setTimeout(() => dataLayer.push({ event: 'render-end' }), 500);
}
};

render() {
const { trackingIdGTM, appState } = this.props;
const { appState } = this.props;

return (
<Helmet
defaultTitle={getInstance()}
defer={false}
onChangeClientState={
trackingIdGTM || appState.webAnalyticsJsPath ? this.trackPage : undefined
}>
onChangeClientState={appState.webAnalyticsJsPath ? this.trackPage : undefined}>
{this.props.children}
</Helmet>
);
}
}

const mapStateToProps = (state: Store) => {
const trackingIdGTM = getGlobalSettingValue(state, 'sonar.analytics.gtm.trackingId');
return {
trackingIdGTM: trackingIdGTM && trackingIdGTM.value
};
};

export default withRouter(connect(mapStateToProps)(withAppStateContext(PageTracker)));
export default withRouter(withAppStateContext(PageTracker));

+ 2
- 1
server/sonar-web/src/main/js/app/components/StartupModal.tsx Переглянути файл

@@ -28,8 +28,9 @@ import { hasMessage } from '../../helpers/l10n';
import { get, save } from '../../helpers/storage';
import { isLoggedIn } from '../../helpers/users';
import { getCurrentUser, Store } from '../../store/rootReducer';
import { AppState } from '../../types/appstate';
import { EditionKey } from '../../types/editions';
import { AppState, CurrentUser } from '../../types/types';
import { CurrentUser } from '../../types/types';
import withAppStateContext from './app-state/withAppStateContext';

const LicensePromptModal = lazyLoadComponent(

+ 20
- 24
server/sonar-web/src/main/js/app/components/__tests__/App-test.tsx Переглянути файл

@@ -19,23 +19,20 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { connect } from 'react-redux';
import { mockAppState } from '../../../helpers/testMocks';
import { App } from '../App';

jest.mock('react-redux', () => ({
connect: jest.fn(() => (a: any) => a)
}));

jest.mock('../../../store/rootReducer', () => ({
getGlobalSettingValue: jest.fn((_, key: string) => ({
value: key === 'sonar.lf.enableGravatar' ? 'true' : 'http://gravatar.com'
}))
}));

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(
shallowRender({ enableGravatar: true, gravatarServerUrl: 'http://example.com' })
shallowRender({
appState: mockAppState({
settings: {
'sonar.lf.enableGravatar': 'true',
'sonar.lf.gravatarServerUrl': 'http://example.com'
}
})
})
).toMatchSnapshot('with gravatar');
});

@@ -44,17 +41,16 @@ it('should correctly set the scrollbar width as a custom property', () => {
expect(document.body.style.getPropertyValue('--sbw')).toBe('0px');
});

describe('redux', () => {
it('should correctly map state props', () => {
const [mapStateToProps] = (connect as jest.Mock).mock.calls[0];

expect(mapStateToProps({})).toEqual({
enableGravatar: true,
gravatarServerUrl: 'http://gravatar.com'
});
});
});

function shallowRender(props: Partial<App['props']> = {}) {
return shallow<App>(<App enableGravatar={false} gravatarServerUrl="" {...props} />);
return shallow<App>(
<App
appState={mockAppState({
settings: {
'sonar.lf.enableGravatar': 'false',
'sonar.lf.gravatarServerUrl': ''
}
})}
{...props}
/>
);
}

+ 0
- 19
server/sonar-web/src/main/js/app/components/__tests__/PageTracker-test.tsx Переглянути файл

@@ -19,7 +19,6 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { gtm } from '../../../helpers/analytics';
import { installScript } from '../../../helpers/extensions';
import { getWebAnalyticsPageHandlerFromCache } from '../../../helpers/extensionsHandler';
import { mockAppState, mockLocation } from '../../../helpers/testMocks';
@@ -33,7 +32,6 @@ jest.mock('../../../helpers/extensionsHandler', () => ({
getWebAnalyticsPageHandlerFromCache: jest.fn().mockReturnValue(undefined)
}));

jest.mock('../../../helpers/analytics', () => ({ gtm: jest.fn() }));
beforeAll(() => {
jest.useFakeTimers();
});
@@ -52,7 +50,6 @@ it('should not trigger if no analytics system is given', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
expect(installScript).not.toHaveBeenCalled();
expect(gtm).not.toHaveBeenCalled();
});

it('should work for WebAnalytics plugin', () => {
@@ -70,22 +67,6 @@ it('should work for WebAnalytics plugin', () => {
expect(pageChange).toHaveBeenCalledWith('/path');
});

it('should work for Google Tag Manager', () => {
(window as any).dataLayer = [];
const { dataLayer } = window as any;
const push = jest.spyOn(dataLayer, 'push');
const wrapper = shallowRender({ trackingIdGTM: '123' });

expect(wrapper.find('Helmet').prop('onChangeClientState')).toBe(wrapper.instance().trackPage);
expect(gtm).toBeCalled();
expect(dataLayer).toHaveLength(0);

wrapper.instance().trackPage();
jest.runAllTimers();
expect(push).toBeCalledWith({ event: 'render-end' });
expect(dataLayer).toHaveLength(1);
});

function shallowRender(props: Partial<PageTracker['props']> = {}) {
return shallow<PageTracker>(
<PageTracker appState={mockAppState()} location={mockLocation()} {...props} />

+ 1
- 1
server/sonar-web/src/main/js/app/components/app-state/AppStateContext.tsx Переглянути файл

@@ -19,7 +19,7 @@
*/

import * as React from 'react';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';

const defaultAppState = {
authenticationError: false,

+ 2
- 15
server/sonar-web/src/main/js/app/components/app-state/AppStateContextProvider.tsx Переглянути файл

@@ -20,29 +20,16 @@
*/

import * as React from 'react';
import { connect } from 'react-redux';
import { setValues } from '../../../apps/settings/store/actions';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { AppStateContext } from './AppStateContext';

export interface AppStateContextProviderProps {
setValues: typeof setValues;
appState: AppState;
}

export function AppStateContextProvider({
setValues,
export default function AppStateContextProvider({
appState,
children
}: React.PropsWithChildren<AppStateContextProviderProps>) {
React.useEffect(() => {
setValues(
Object.keys(appState.settings),
Object.entries(appState.settings).map(([key, value]) => ({ key, value }))
);
});

return <AppStateContext.Provider value={appState}>{children}</AppStateContext.Provider>;
}
const mapDispatchToProps = { setValues };
export default connect(null, mapDispatchToProps)(AppStateContextProvider);

+ 6
- 8
server/sonar-web/src/main/js/app/components/app-state/__tests__/AppStateContextProvider-test.tsx Переглянути файл

@@ -21,20 +21,18 @@ import { mount } from 'enzyme';
import * as React from 'react';
import { mockAppState } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { AppStateContextProvider, AppStateContextProviderProps } from '../AppStateContextProvider';
import AppStateContextProvider, { AppStateContextProviderProps } from '../AppStateContextProvider';

it('should set value correctly', async () => {
const setValues = jest.fn();
const appState = mockAppState({ settings: { 'sonar.lf.logoUrl': 'whatevs/' } });
const wrapper = render({
appState: mockAppState({ settings: { foo: 'bar' } }),
setValues
appState
});
await waitAndUpdate(wrapper);
expect(setValues).toHaveBeenCalledWith(['foo'], [{ key: 'foo', value: 'bar' }]);

expect(wrapper).toMatchSnapshot();
});

function render(override?: Partial<AppStateContextProviderProps>) {
return mount(
<AppStateContextProvider appState={mockAppState()} setValues={jest.fn()} {...override} />
);
return mount(<AppStateContextProvider appState={mockAppState()} {...override} />);
}

+ 19
- 0
server/sonar-web/src/main/js/app/components/app-state/__tests__/__snapshots__/AppStateContextProvider-test.tsx.snap Переглянути файл

@@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should set value correctly 1`] = `
<AppStateContextProvider
appState={
Object {
"edition": "community",
"productionDatabase": true,
"qualifiers": Array [
"TRK",
],
"settings": Object {
"sonar.lf.logoUrl": "whatevs/",
},
"version": "1.0",
}
}
/>
`;

+ 1
- 1
server/sonar-web/src/main/js/app/components/app-state/__tests__/withAppStateContext-test.tsx Переглянути файл

@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockAppState } from '../../../../helpers/testMocks';
import { AppState } from '../../../../types/types';
import { AppState } from '../../../../types/appstate';
import withAppStateContext from '../withAppStateContext';

const appState = mockAppState();

+ 1
- 1
server/sonar-web/src/main/js/app/components/app-state/withAppStateContext.tsx Переглянути файл

@@ -20,7 +20,7 @@

import * as React from 'react';
import { getWrappedDisplayName } from '../../../components/hoc/utils';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { AppStateContext } from './AppStateContext';

export interface WithAppStateContextProps {

+ 2
- 1
server/sonar-web/src/main/js/app/components/extensions/Extension.tsx Переглянути файл

@@ -27,8 +27,9 @@ import { getCurrentL10nBundle, translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { addGlobalErrorMessage } from '../../../store/globalMessages';
import { getCurrentUser, Store } from '../../../store/rootReducer';
import { AppState } from '../../../types/appstate';
import { ExtensionStartMethod } from '../../../types/extension';
import { AppState, CurrentUser, Dict, Extension as TypeExtension } from '../../../types/types';
import { CurrentUser, Dict, Extension as TypeExtension } from '../../../types/types';
import * as theme from '../../theme';
import getStore from '../../utils/getStore';
import withAppStateContext from '../app-state/withAppStateContext';

+ 1
- 1
server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx Переглянути файл

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import withAppStateContext from '../app-state/withAppStateContext';
import NotFound from '../NotFound';
import Extension from './Extension';

+ 2
- 0
server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts Переглянути файл

@@ -70,6 +70,7 @@ import DateFormatter from '../../../components/intl/DateFormatter';
import DateFromNow from '../../../components/intl/DateFromNow';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import Measure from '../../../components/measure/Measure';
import RatingTooltipContent from '../../../components/measure/RatingTooltipContent';
import { Alert } from '../../../components/ui/Alert';
import CoverageRating from '../../../components/ui/CoverageRating';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
@@ -237,6 +238,7 @@ const exposeLibraries = () => {
Radio,
RadioToggle,
Rating,
RatingTooltipContent,
ReloadButton,
ResetButtonLink,
SearchBox,

+ 1
- 1
server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx Переглянути файл

@@ -19,8 +19,8 @@
*/
/* eslint-disable react/no-unused-state */
import * as React from 'react';
import { AppState } from '../../../types/appstate';
import { IndexationContextInterface, IndexationStatus } from '../../../types/indexation';
import { AppState } from '../../../types/types';
import withAppStateContext from '../app-state/withAppStateContext';
import { IndexationContext } from './IndexationContext';
import IndexationNotificationHelper from './IndexationNotificationHelper';

+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx Переглянути файл

@@ -22,9 +22,9 @@ import { Link } from 'react-router';
import { isValidLicense } from '../../../../api/marketplace';
import { Alert } from '../../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { AppState } from '../../../../types/appstate';
import { ComponentQualifier } from '../../../../types/component';
import { Task } from '../../../../types/tasks';
import { AppState } from '../../../../types/types';
import withAppStateContext from '../../app-state/withAppStateContext';

interface Props {

+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx Переглянути файл

@@ -30,9 +30,10 @@ import NavBarTabs from '../../../../components/ui/NavBarTabs';
import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like';
import { hasMessage, translate, translateWithParameters } from '../../../../helpers/l10n';
import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls';
import { AppState } from '../../../../types/appstate';
import { BranchLike, BranchParameters } from '../../../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component';
import { AppState, Component, Extension } from '../../../../types/types';
import { Component, Extension } from '../../../../types/types';
import withAppStateContext from '../../app-state/withAppStateContext';
import './Menu.css';


+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx Переглянути файл

@@ -21,8 +21,9 @@ import classNames from 'classnames';
import * as React from 'react';
import Toggler from '../../../../../components/controls/Toggler';
import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
import { AppState } from '../../../../../types/appstate';
import { BranchLike } from '../../../../../types/branch-like';
import { AppState, Component } from '../../../../../types/types';
import { Component } from '../../../../../types/types';
import withAppStateContext from '../../../app-state/withAppStateContext';
import './BranchLikeNavigation.css';
import CurrentBranchLike from './CurrentBranchLike';

+ 10
- 16
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx Переглянути файл

@@ -18,18 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system';
import { getGlobalSettingValue, Store } from '../../../../store/rootReducer';
import { AppState } from '../../../../types/appstate';
import { GlobalSettingKeys } from '../../../../types/settings';
import withAppStateContext from '../../app-state/withAppStateContext';

interface StateProps {
customLogoUrl?: string;
customLogoWidth?: string | number;
export interface GlobalNavBrandingProps {
appState: AppState;
}

export function GlobalNavBranding({ customLogoUrl, customLogoWidth }: StateProps) {
export function GlobalNavBranding({ appState: { settings } }: GlobalNavBrandingProps) {
const customLogoUrl = settings[GlobalSettingKeys.LogoUrl];
const customLogoWidth = settings[GlobalSettingKeys.LogoWidth];

const title = translate('layout.sonar.slogan');
const url = customLogoUrl || `${getBaseUrl()}/images/logo.svg?v=6.6`;
const width = customLogoUrl ? customLogoWidth || 100 : 83;
@@ -41,13 +44,4 @@ export function GlobalNavBranding({ customLogoUrl, customLogoWidth }: StateProps
);
}

const mapStateToProps = (state: Store): StateProps => {
const customLogoUrl = getGlobalSettingValue(state, 'sonar.lf.logoUrl');
const customLogoWidth = getGlobalSettingValue(state, 'sonar.lf.logoWidthPx');
return {
customLogoUrl: customLogoUrl && customLogoUrl.value,
customLogoWidth: customLogoWidth && customLogoWidth.value
};
};

export default connect(mapStateToProps)(GlobalNavBranding);
export default withAppStateContext(GlobalNavBranding);

+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx Переглянути файл

@@ -25,8 +25,9 @@ import Dropdown from '../../../../components/controls/Dropdown';
import DropdownIcon from '../../../../components/icons/DropdownIcon';
import { translate } from '../../../../helpers/l10n';
import { getQualityGatesUrl } from '../../../../helpers/urls';
import { AppState } from '../../../../types/appstate';
import { ComponentQualifier } from '../../../../types/component';
import { AppState, CurrentUser, Extension } from '../../../../types/types';
import { CurrentUser, Extension } from '../../../../types/types';
import withAppStateContext from '../../app-state/withAppStateContext';

interface Props {

+ 50
- 0
server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavBranding-test.tsx Переглянути файл

@@ -0,0 +1,50 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import { mockAppState } from '../../../../../helpers/testMocks';
import { GlobalNavBranding, GlobalNavBrandingProps } from '../GlobalNavBranding';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(
shallowRender({
appState: mockAppState({
settings: {
'sonar.lf.logoUrl': 'http://sonarsource.com/custom-logo.svg'
}
})
})
).toMatchSnapshot('with logo');
expect(
shallowRender({
appState: mockAppState({
settings: {
'sonar.lf.logoUrl': 'http://sonarsource.com/custom-logo.svg',
'sonar.lf.logoWidthPx': '200'
}
})
})
).toMatchSnapshot('with logo and width');
});

function shallowRender(overrides: Partial<GlobalNavBrandingProps> = {}) {
return shallow(<GlobalNavBranding appState={mockAppState()} {...overrides} />);
}

+ 2
- 2
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNav-test.tsx.snap Переглянути файл

@@ -6,7 +6,7 @@ exports[`should render correctly: anonymous users 1`] = `
height={48}
id="global-navigation"
>
<Connect(GlobalNavBranding) />
<withAppStateContext(GlobalNavBranding) />
<withAppStateContext(GlobalNavMenu)
currentUser={
Object {
@@ -47,7 +47,7 @@ exports[`should render correctly: logged in users 1`] = `
height={48}
id="global-navigation"
>
<Connect(GlobalNavBranding) />
<withAppStateContext(GlobalNavBranding) />
<withAppStateContext(GlobalNavMenu)
currentUser={
Object {

+ 52
- 0
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap Переглянути файл

@@ -0,0 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<Link
className="navbar-brand"
onlyActiveOnIndex={false}
style={Object {}}
to="/"
>
<img
alt="layout.sonar.slogan"
height={30}
src="/images/logo.svg?v=6.6"
title="layout.sonar.slogan"
width={83}
/>
</Link>
`;

exports[`should render correctly: with logo 1`] = `
<Link
className="navbar-brand"
onlyActiveOnIndex={false}
style={Object {}}
to="/"
>
<img
alt="layout.sonar.slogan"
height={30}
src="http://sonarsource.com/custom-logo.svg"
title="layout.sonar.slogan"
width={100}
/>
</Link>
`;

exports[`should render correctly: with logo and width 1`] = `
<Link
className="navbar-brand"
onlyActiveOnIndex={false}
style={Object {}}
to="/"
>
<img
alt="layout.sonar.slogan"
height={30}
src="http://sonarsource.com/custom-logo.svg"
title="layout.sonar.slogan"
width="200"
/>
</Link>
`;

+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.tsx.snap Переглянути файл

@@ -60,7 +60,7 @@ exports[`should render the right interface for logged in user 1`] = `
href="#"
title="Skywalker"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
name="Skywalker"
size={32}
/>

+ 2
- 1
server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx Переглянути файл

@@ -27,9 +27,10 @@ import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton
import { sortUpgrades, UpdateUseCase } from '../../../components/upgrade/utils';
import { translate } from '../../../helpers/l10n';
import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users';
import { AppState } from '../../../types/appstate';
import { Permissions } from '../../../types/permissions';
import { SystemUpgrade } from '../../../types/system';
import { AppState, CurrentUser, Dict } from '../../../types/types';
import { CurrentUser, Dict } from '../../../types/types';
import withAppStateContext from '../app-state/withAppStateContext';
import './UpdateNotification.css';


+ 1
- 1
server/sonar-web/src/main/js/app/index.ts Переглянути файл

@@ -21,7 +21,7 @@ import { installExtensionsHandler, installWebAnalyticsHandler } from '../helpers
import { loadL10nBundle } from '../helpers/l10n';
import { parseJSON, request } from '../helpers/request';
import { getBaseUrl, getSystemStatus } from '../helpers/system';
import { AppState } from '../types/types';
import { AppState } from '../types/appstate';
import './styles/sonar.ts';

installWebAnalyticsHandler();

+ 2
- 1
server/sonar-web/src/main/js/app/utils/startReactApp.tsx Переглянути файл

@@ -60,7 +60,8 @@ import webhooksRoutes from '../../apps/webhooks/routes';
import withIndexationGuard from '../../components/hoc/withIndexationGuard';
import { lazyLoadComponent } from '../../components/lazyLoadComponent';
import getHistory from '../../helpers/getHistory';
import { AppState, CurrentUser } from '../../types/types';
import { AppState } from '../../types/appstate';
import { CurrentUser } from '../../types/types';
import App from '../components/App';
import AppStateContextProvider from '../components/app-state/AppStateContextProvider';
import GlobalContainer from '../components/GlobalContainer';

+ 28
- 40
server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx Переглянути файл

@@ -18,60 +18,63 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { connect } from 'react-redux';
import { getGlobalSettingValue, Store } from '../../../store/rootReducer';
import { getValues } from '../../../api/settings';
import { AdminPageExtension } from '../../../types/extension';
import { SettingsKey } from '../../../types/settings';
import { Extension } from '../../../types/types';
import { fetchValues } from '../../settings/store/actions';
import '../style.css';
import { HousekeepingPolicy, RangeOption } from '../utils';
import AuditAppRenderer from './AuditAppRenderer';

interface Props {
auditHousekeepingPolicy: HousekeepingPolicy;
fetchValues: typeof fetchValues;
adminPages: Extension[];
}

interface State {
dateRange?: { from?: Date; to?: Date };
hasGovernanceExtension?: boolean;
downloadStarted: boolean;
housekeepingPolicy: HousekeepingPolicy;
selection: RangeOption;
}

export class AuditApp extends React.PureComponent<Props, State> {
export default class AuditApp extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
const hasGovernanceExtension = Boolean(
props.adminPages?.find(e => e.key === AdminPageExtension.GovernanceConsole)
);

this.state = {
downloadStarted: false,
selection: RangeOption.Today,
hasGovernanceExtension
housekeepingPolicy: HousekeepingPolicy.Monthly,
selection: RangeOption.Today
};
}

componentDidMount() {
const { hasGovernanceExtension } = this.state;

if (hasGovernanceExtension) {
this.props.fetchValues(['sonar.dbcleaner.auditHousekeeping']);
if (this.hasGovernanceExtension()) {
this.fetchHouseKeepingPolicy();
}
}

componentDidUpdate(prevProps: Props) {
if (prevProps.adminPages !== this.props.adminPages) {
const hasGovernanceExtension = Boolean(
this.props.adminPages?.find(e => e.key === AdminPageExtension.GovernanceConsole)
);
this.setState({
hasGovernanceExtension
});
if (prevProps.adminPages !== this.props.adminPages && this.hasGovernanceExtension()) {
this.fetchHouseKeepingPolicy();
}
}

fetchHouseKeepingPolicy = async () => {
const results = await getValues({ keys: SettingsKey.AuditHouseKeeping });

this.setState({
housekeepingPolicy:
(results[0]?.value as HousekeepingPolicy | undefined) ?? HousekeepingPolicy.Monthly
});
};

hasGovernanceExtension = () => {
return Boolean(
this.props.adminPages?.find(e => e.key === AdminPageExtension.GovernanceConsole)
);
};

handleDateSelection = (dateRange: { from?: Date; to?: Date }) =>
this.setState({ dateRange, downloadStarted: false, selection: RangeOption.Custom });

@@ -85,10 +88,7 @@ export class AuditApp extends React.PureComponent<Props, State> {
};

render() {
const { hasGovernanceExtension, ...auditAppRendererProps } = this.state;
const { auditHousekeepingPolicy } = this.props;

if (!hasGovernanceExtension) {
if (!this.hasGovernanceExtension()) {
return null;
}

@@ -97,20 +97,8 @@ export class AuditApp extends React.PureComponent<Props, State> {
handleDateSelection={this.handleDateSelection}
handleOptionSelection={this.handleOptionSelection}
handleStartDownload={this.handleStartDownload}
housekeepingPolicy={auditHousekeepingPolicy || HousekeepingPolicy.Monthly}
{...auditAppRendererProps}
{...this.state}
/>
);
}
}

const mapDispatchToProps = { fetchValues };

const mapStateToProps = (state: Store) => {
const settingValue = getGlobalSettingValue(state, 'sonar.dbcleaner.auditHousekeeping');
return {
auditHousekeepingPolicy: settingValue?.value as HousekeepingPolicy
};
};

export default connect(mapStateToProps, mapDispatchToProps)(AuditApp);

+ 32
- 10
server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-test.tsx Переглянути файл

@@ -20,30 +20,41 @@
import { subDays } from 'date-fns';
import { shallow } from 'enzyme';
import * as React from 'react';
import { getValues } from '../../../../api/settings';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { AdminPageExtension } from '../../../../types/extension';
import { HousekeepingPolicy, RangeOption } from '../../utils';
import { AuditApp } from '../AuditApp';
import AuditApp from '../AuditApp';
import AuditAppRenderer from '../AuditAppRenderer';

jest.mock('../../../../api/settings', () => ({
getValues: jest.fn().mockResolvedValue([])
}));

beforeEach(() => {
jest.clearAllMocks();
});

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should do nothing if governance is not available', async () => {
const fetchValues = jest.fn();
const wrapper = shallowRender({ fetchValues, adminPages: [] });
const wrapper = shallowRender({ adminPages: [] });
await waitAndUpdate(wrapper);

expect(wrapper.type()).toBeNull();
expect(fetchValues).not.toBeCalled();
expect(getValues).not.toBeCalled();
});

it('should fetch houskeeping policy on mount', async () => {
const fetchValues = jest.fn();
const wrapper = shallowRender({ fetchValues });
it('should handle housekeeping policy', async () => {
(getValues as jest.Mock).mockResolvedValueOnce([{ value: HousekeepingPolicy.Weekly }]);

const wrapper = shallowRender();

await waitAndUpdate(wrapper);
expect(fetchValues).toBeCalled();

expect(wrapper.find(AuditAppRenderer).props().housekeepingPolicy).toBe(HousekeepingPolicy.Weekly);
});

it('should handle date selection', () => {
@@ -76,11 +87,22 @@ it('should handle predefined selection', () => {
expect(wrapper.state().dateRange).toBeUndefined();
});

it('should handle update to admin pages', async () => {
const wrapper = shallowRender({ adminPages: [] });
await waitAndUpdate(wrapper);

expect(wrapper.type()).toBeNull();
expect(getValues).not.toBeCalled();

wrapper.setProps({ adminPages: [{ key: AdminPageExtension.GovernanceConsole, name: 'name' }] });
await waitAndUpdate(wrapper);

expect(getValues).toBeCalled();
});

function shallowRender(props: Partial<AuditApp['props']> = {}) {
return shallow<AuditApp>(
<AuditApp
auditHousekeepingPolicy={HousekeepingPolicy.Monthly}
fetchValues={jest.fn()}
adminPages={[{ key: AdminPageExtension.GovernanceConsole, name: 'name' }]}
{...props}
/>

+ 1
- 1
server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx Переглянути файл

@@ -24,7 +24,7 @@ import { ClearButton } from '../../../components/controls/buttons';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';

export interface Props {
appState: AppState;

+ 1
- 1
server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx Переглянути файл

@@ -21,7 +21,7 @@ import * as React from 'react';
import { changePassword } from '../../api/users';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { Location, withRouter } from '../../components/hoc/withRouter';
import { AppState } from '../../types/types';
import { AppState } from '../../types/appstate';
import ChangeAdminPasswordAppRenderer from './ChangeAdminPasswordAppRenderer';
import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from './constants';


+ 2
- 1
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx Переглянути файл

@@ -26,7 +26,8 @@ import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { getIssuesUrl } from '../../../helpers/urls';
import { AppState, RuleDetails } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { RuleDetails } from '../../../types/types';

interface Props {
appState: AppState;

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx Переглянути файл

@@ -24,7 +24,7 @@ import ChevronsIcon from '../../../components/icons/ChevronsIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { AlmKeys } from '../../../types/alm-settings';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { CreateProjectModes } from './types';

export interface CreateProjectModeSelectionProps {

+ 2
- 1
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx Переглянути файл

@@ -27,7 +27,8 @@ import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import { AppState, LoggedInUser } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { LoggedInUser } from '../../../types/types';
import AlmBindingDefinitionForm from '../../settings/components/almIntegration/AlmBindingDefinitionForm';
import AzureProjectCreate from './AzureProjectCreate';
import BitbucketCloudProjectCreate from './BitbucketCloudProjectCreate';

+ 1
- 1
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap Переглянути файл

@@ -65,7 +65,7 @@ exports[`should show warnning when not all projects are accessible 1`] = `
displayReset={true}
onReset={[Function]}
/>
<Connect(Sidebar)
<withAppStateContext(Sidebar)
component={
Object {
"breadcrumbs": Array [],

+ 11
- 16
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx Переглянути файл

@@ -18,9 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { connect } from 'react-redux';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { isBranch, isPullRequest } from '../../../helpers/branch-like';
import { getGlobalSettingValue, Store } from '../../../store/rootReducer';
import { AppState } from '../../../types/appstate';
import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier, isApplication, isPortfolioLike } from '../../../types/component';
import {
@@ -29,6 +29,7 @@ import {
ReferencedLanguage,
ReferencedRule
} from '../../../types/issues';
import { GlobalSettingKeys } from '../../../types/settings';
import { Component, Dict, UserBase } from '../../../types/types';
import { Query } from '../utils';
import AssigneeFacet from './AssigneeFacet';
@@ -48,6 +49,7 @@ import TagFacet from './TagFacet';
import TypeFacet from './TypeFacet';

export interface Props {
appState: AppState;
branchLike?: BranchLike;
component: Component | undefined;
createdAfterIncludesTime: boolean;
@@ -64,7 +66,6 @@ export interface Props {
referencedLanguages: Dict<ReferencedLanguage>;
referencedRules: Dict<ReferencedRule>;
referencedUsers: Dict<UserBase>;
disableDeveloperAggregatedInfo: boolean;
}

export class Sidebar extends React.PureComponent<Props> {
@@ -108,6 +109,7 @@ export class Sidebar extends React.PureComponent<Props> {

render() {
const {
appState: { settings },
component,
createdAfterIncludesTime,
facets,
@@ -116,6 +118,9 @@ export class Sidebar extends React.PureComponent<Props> {
branchLike
} = this.props;

const disableDeveloperAggregatedInfo =
settings[GlobalSettingKeys.DeveloperAggregatedInfoDisabled] === 'true';

const branch =
(isBranch(branchLike) && branchLike.name) ||
(isPullRequest(branchLike) && branchLike.branch) ||
@@ -255,7 +260,7 @@ export class Sidebar extends React.PureComponent<Props> {
/>
)}
{this.renderComponentFacets()}
{!this.props.myIssues && !this.props.disableDeveloperAggregatedInfo && (
{!this.props.myIssues && !disableDeveloperAggregatedInfo && (
<AssigneeFacet
assigned={query.assigned}
assignees={query.assignees}
@@ -269,7 +274,7 @@ export class Sidebar extends React.PureComponent<Props> {
stats={facets.assignees}
/>
)}
{displayAuthorFacet && !this.props.disableDeveloperAggregatedInfo && (
{displayAuthorFacet && !disableDeveloperAggregatedInfo && (
<AuthorFacet
author={query.author}
component={component}
@@ -287,14 +292,4 @@ export class Sidebar extends React.PureComponent<Props> {
}
}

export const mapStateToProps = (state: Store) => {
const disableDeveloperAggregatedInfo = getGlobalSettingValue(
state,
'sonar.developerAggregatedInfo.disabled'
);
return {
disableDeveloperAggregatedInfo: disableDeveloperAggregatedInfo?.value === true.toString()
};
};

export default connect(mapStateToProps)(Sidebar);
export default withAppStateContext(Sidebar);

+ 13
- 15
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-test.tsx Переглянути файл

@@ -21,12 +21,11 @@ import { shallow, ShallowWrapper } from 'enzyme';
import { flatten } from 'lodash';
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
import { getGlobalSettingValue } from '../../../../store/rootReducer';
import { mockAppState } from '../../../../helpers/testMocks';
import { GlobalSettingKeys } from '../../../../types/settings';
import { ComponentQualifier } from '../../../../types/component';
import { Query } from '../../utils';
import { mapStateToProps, Sidebar } from '../Sidebar';

jest.mock('../../../../store/rootReducer', () => ({ getGlobalSettingValue: jest.fn() }));
import { Sidebar } from '../Sidebar';

it('should render facets for global page', () => {
expect(renderSidebar()).toMatchSnapshot();
@@ -52,16 +51,13 @@ it('should render facets when my issues are selected', () => {
});

it('should not render developer nominative facets when asked not to', () => {
expect(renderSidebar({ disableDeveloperAggregatedInfo: true })).toMatchSnapshot();
});

it('should init the component with the proper store value', () => {
mapStateToProps({} as any);

expect(getGlobalSettingValue).toHaveBeenCalledWith(
expect.any(Object),
'sonar.developerAggregatedInfo.disabled'
);
expect(
renderSidebar({
appState: mockAppState({
settings: { [GlobalSettingKeys.DeveloperAggregatedInfoDisabled]: 'true' }
})
})
).toMatchSnapshot();
});

const renderSidebar = (props?: Partial<Sidebar['props']>) => {
@@ -69,6 +65,9 @@ const renderSidebar = (props?: Partial<Sidebar['props']>) => {
mapChildren(
shallow<Sidebar>(
<Sidebar
appState={mockAppState({
settings: { [GlobalSettingKeys.DeveloperAggregatedInfoDisabled]: 'false' }
})}
component={undefined}
createdAfterIncludesTime={false}
facets={{}}
@@ -84,7 +83,6 @@ const renderSidebar = (props?: Partial<Sidebar['props']>) => {
referencedLanguages={{}}
referencedRules={{}}
referencedUsers={{}}
disableDeveloperAggregatedInfo={false}
{...props}
/>
)

+ 4
- 4
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.tsx.snap Переглянути файл

@@ -41,7 +41,7 @@ exports[`should render 1`] = `

exports[`test behavior should correctly render facet item 1`] = `
<React.Fragment>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Name Baz"
size={16}
@@ -52,7 +52,7 @@ exports[`test behavior should correctly render facet item 1`] = `

exports[`test behavior should correctly render facet item 2`] = `
<React.Fragment>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="foo"
size={16}
@@ -63,7 +63,7 @@ exports[`test behavior should correctly render facet item 2`] = `

exports[`test behavior should correctly render search result correctly 1`] = `
<React.Fragment>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Name Bar"
size={16}
@@ -80,7 +80,7 @@ exports[`test behavior should correctly render search result correctly 1`] = `

exports[`test behavior should correctly render search result correctly 2`] = `
<React.Fragment>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="foo"
size={16}

server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx → server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx Переглянути файл

@@ -18,35 +18,27 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { connect } from 'react-redux';
import AdminContext from '../../app/components/AdminContext';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { getGlobalSettingValue, Store } from '../../store/rootReducer';
import { AppState } from '../../types/appstate';
import { EditionKey } from '../../types/editions';
import { AppState, RawQuery } from '../../types/types';
import { fetchValues } from '../settings/store/actions';
import { GlobalSettingKeys } from '../../types/settings';
import { RawQuery } from '../../types/types';
import App from './App';

interface OwnProps {
export interface MarketplaceAppContainerProps {
location: { pathname: string; query: RawQuery };
appState: AppState;
}

interface StateToProps {
fetchValues: typeof fetchValues;
updateCenterActive: boolean;
}

function WithAdminContext(props: StateToProps & OwnProps) {
React.useEffect(() => {
props.fetchValues(['sonar.updatecenter.activate']);
});
export function MarketplaceAppContainer(props: MarketplaceAppContainerProps) {
const { appState, location } = props;

const propsToPass = {
location: props.location,
updateCenterActive: props.updateCenterActive,
currentEdition: props.appState.edition as EditionKey,
standaloneMode: props.appState.standalone
location,
updateCenterActive: appState.settings[GlobalSettingKeys.UpdatecenterActivated] === 'true',
currentEdition: appState.edition as EditionKey,
standaloneMode: appState.standalone
};

return (
@@ -62,13 +54,4 @@ function WithAdminContext(props: StateToProps & OwnProps) {
);
}

const mapDispatchToProps = { fetchValues };

const mapStateToProps = (state: Store) => {
const updateCenterActive = getGlobalSettingValue(state, 'sonar.updatecenter.activate');
return {
updateCenterActive: Boolean(updateCenterActive && updateCenterActive.value === 'true')
};
};

export default connect(mapStateToProps, mapDispatchToProps)(withAppStateContext(WithAdminContext));
export default withAppStateContext(MarketplaceAppContainer);

+ 0
- 52
server/sonar-web/src/main/js/apps/marketplace/__tests__/AppContainer-test.tsx Переглянути файл

@@ -1,52 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { connect } from 'react-redux';
import { mockStore } from '../../../helpers/testMocks';
import { getGlobalSettingValue } from '../../../store/rootReducer';
import '../AppContainer';

jest.mock('react-redux', () => ({
connect: jest.fn(() => (a: any) => a)
}));

jest.mock('../../../store/rootReducer', () => {
return {
getGlobalSettingValue: jest.fn()
};
});

describe('redux', () => {
it('should correctly map state and dispatch props', () => {
const store = mockStore();
const updateCenterActive = true;
(getGlobalSettingValue as jest.Mock).mockReturnValueOnce({
value: `${updateCenterActive}`
});

const [mapStateToProps] = (connect as jest.Mock).mock.calls[0];

const props = mapStateToProps(store);
expect(props).toEqual({
updateCenterActive
});

expect(getGlobalSettingValue).toHaveBeenCalledWith(store, 'sonar.updatecenter.activate');
});
});

+ 48
- 0
server/sonar-web/src/main/js/apps/marketplace/__tests__/MarketplaceAppContainer-test.tsx Переглянути файл

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import React from 'react';
import { mockAppState, mockLocation } from '../../../helpers/testMocks';
import { GlobalSettingKeys } from '../../../types/settings';
import { EditionKey } from '../../../types/editions';
import { MarketplaceAppContainer, MarketplaceAppContainerProps } from '../MarketplaceAppContainer';

it('should render correctly', () => {
expect(shallowRender().dive()).toMatchSnapshot('default');
expect(
shallowRender({
appState: mockAppState({
settings: {
[GlobalSettingKeys.UpdatecenterActivated]: 'true'
}
})
}).dive()
).toMatchSnapshot('update center active');
});

function shallowRender(overrides: Partial<MarketplaceAppContainerProps> = {}) {
return shallow<MarketplaceAppContainerProps>(
<MarketplaceAppContainer
appState={mockAppState({ edition: EditionKey.community, standalone: true })}
location={mockLocation()}
{...overrides}
/>
);
}

+ 54
- 0
server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/MarketplaceAppContainer-test.tsx.snap Переглянути файл

@@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<withRouter(App)
currentEdition="community"
fetchPendingPlugins={[Function]}
location={
Object {
"action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
"query": Object {},
"search": "",
"state": Object {},
}
}
pendingPlugins={
Object {
"installing": Array [],
"removing": Array [],
"updating": Array [],
}
}
standaloneMode={true}
updateCenterActive={false}
/>
`;

exports[`should render correctly: update center active 1`] = `
<withRouter(App)
currentEdition="community"
fetchPendingPlugins={[Function]}
location={
Object {
"action": "PUSH",
"hash": "",
"key": "key",
"pathname": "/path",
"query": Object {},
"search": "",
"state": Object {},
}
}
pendingPlugins={
Object {
"installing": Array [],
"removing": Array [],
"updating": Array [],
}
}
updateCenterActive={true}
/>
`;

+ 1
- 1
server/sonar-web/src/main/js/apps/marketplace/routes.ts Переглянути файл

@@ -21,7 +21,7 @@ import { lazyLoadComponent } from '../../components/lazyLoadComponent';

const routes = [
{
indexRoute: { component: lazyLoadComponent(() => import('./AppContainer')) }
indexRoute: { component: lazyLoadComponent(() => import('./MarketplaceAppContainer')) }
}
];


+ 2
- 1
server/sonar-web/src/main/js/apps/overview/components/App.tsx Переглянути файл

@@ -24,9 +24,10 @@ import { Router, withRouter } from '../../../components/hoc/withRouter';
import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
import { isPullRequest } from '../../../helpers/branch-like';
import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
import { AppState } from '../../../types/appstate';
import { BranchLike } from '../../../types/branch-like';
import { isPortfolioLike } from '../../../types/component';
import { AppState, Component } from '../../../types/types';
import { Component } from '../../../types/types';
import BranchOverview from '../branches/BranchOverview';

const EmptyOverview = lazyLoadComponent(() => import('./EmptyOverview'));

+ 3
- 3
server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx Переглянути файл

@@ -19,7 +19,8 @@
*/
import * as React from 'react';
import Tooltip from '../../../components/controls/Tooltip';
import { getLeakValue, getRatingTooltip } from '../../../components/measure/utils';
import RatingTooltipContent from '../../../components/measure/RatingTooltipContent';
import { getLeakValue } from '../../../components/measure/utils';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import Rating from '../../../components/ui/Rating';
import { findMeasure } from '../../../helpers/measures';
@@ -50,10 +51,9 @@ function renderRatingLink(props: IssueRatingProps) {
}

const value = measure && (useDiffMetric ? getLeakValue(measure) : measure.value);
const tooltip = value && getRatingTooltip(rating, Number(value));

return (
<Tooltip overlay={tooltip}>
<Tooltip overlay={value && <RatingTooltipContent metricKey={rating} value={value} />}>
<span>
<DrilldownLink
branchLike={branchLike}

+ 36
- 6
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap Переглянути файл

@@ -8,7 +8,12 @@ exports[`should render correctly for bugs 1`] = `
metric_domain.Reliability
</span>
<Tooltip
overlay="metric.reliability_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="reliability_rating"
value="1.0"
/>
}
>
<span>
<DrilldownLink
@@ -43,7 +48,12 @@ exports[`should render correctly for bugs 2`] = `
metric_domain.Reliability
</span>
<Tooltip
overlay="metric.reliability_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="new_reliability_rating"
value="1.0"
/>
}
>
<span>
<DrilldownLink
@@ -78,7 +88,12 @@ exports[`should render correctly for code smells 1`] = `
metric_domain.Maintainability
</span>
<Tooltip
overlay="metric.sqale_rating.tooltip.A.0.0%"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="sqale_rating"
value="1.0"
/>
}
>
<span>
<DrilldownLink
@@ -113,7 +128,12 @@ exports[`should render correctly for code smells 2`] = `
metric_domain.Maintainability
</span>
<Tooltip
overlay="metric.sqale_rating.tooltip.A.0.0%"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="new_maintainability_rating"
value="1.0"
/>
}
>
<span>
<DrilldownLink
@@ -148,7 +168,12 @@ exports[`should render correctly for vulnerabilities 1`] = `
metric_domain.Security
</span>
<Tooltip
overlay="metric.security_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="security_rating"
value="1.0"
/>
}
>
<span>
<DrilldownLink
@@ -183,7 +208,12 @@ exports[`should render correctly for vulnerabilities 2`] = `
metric_domain.Security
</span>
<Tooltip
overlay="metric.security_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="new_security_rating"
value="1.0"
/>
}
>
<span>
<DrilldownLink

+ 2
- 1
server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx Переглянути файл

@@ -24,7 +24,8 @@ import { getPermissionTemplates } from '../../../api/permissions';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
import { AppState, Permission, PermissionTemplate } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { Permission, PermissionTemplate } from '../../../types/types';
import '../../permissions/styles.css';
import { mergeDefaultsToTemplates, mergePermissionsToTemplates, sortPermissions } from '../utils';
import Home from './Home';

+ 2
- 1
server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx Переглянути файл

@@ -20,8 +20,9 @@
import * as React from 'react';
import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import ListFooter from '../../../../components/controls/ListFooter';
import { AppState } from '../../../../types/appstate';
import { ComponentQualifier } from '../../../../types/component';
import { AppState, Paging, PermissionGroup, PermissionUser } from '../../../../types/types';
import { Paging, PermissionGroup, PermissionUser } from '../../../../types/types';
import HoldersList from '../../shared/components/HoldersList';
import SearchForm from '../../shared/components/SearchForm';
import {

+ 1
- 1
server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap Переглянути файл

@@ -90,7 +90,7 @@ exports[`should render correctly: default 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="text-middle big-spacer-right flex-0"
name="John Doe"
size={36}

+ 1
- 1
server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx Переглянути файл

@@ -27,9 +27,9 @@ import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { isBranch, sortBranches } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/appstate';
import { Branch, BranchLike } from '../../../types/branch-like';
import {
AppState,
Component,
NewCodePeriod,
NewCodePeriodSettingType,

+ 1
- 1
server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx Переглянути файл

@@ -20,8 +20,8 @@
import * as React from 'react';
import { getValues } from '../../../api/settings';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { AppState } from '../../../types/appstate';
import { SettingsKey } from '../../../types/settings';
import { AppState } from '../../../types/types';
import LifetimeInformationRenderer from './LifetimeInformationRenderer';

interface Props {

+ 2
- 1
server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx Переглянути файл

@@ -23,9 +23,10 @@ import { getStatus } from '../../api/project-dump';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import throwGlobalError from '../../app/utils/throwGlobalError';
import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { DumpStatus, DumpTask } from '../../types/project-dump';
import { TaskStatuses, TaskTypes } from '../../types/tasks';
import { AppState, Component } from '../../types/types';
import { Component } from '../../types/types';
import Export from './components/Export';
import Import from './components/Import';
import './styles.css';

+ 2
- 1
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx Переглянути файл

@@ -33,8 +33,9 @@ import { translate } from '../../../helpers/l10n';
import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages';
import { get, save } from '../../../helpers/storage';
import { isLoggedIn } from '../../../helpers/users';
import { AppState } from '../../../types/appstate';
import { ComponentQualifier } from '../../../types/component';
import { AppState, CurrentUser, RawQuery } from '../../../types/types';
import { CurrentUser, RawQuery } from '../../../types/types';
import { hasFilterParams, hasViewParams, parseUrlQuery, Query } from '../query';
import '../styles.css';
import { Facets, Project } from '../types';

+ 2
- 1
server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx Переглянути файл

@@ -27,9 +27,10 @@ import { Router, withRouter } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { getComponentAdminUrl, getComponentOverviewUrl } from '../../../helpers/urls';
import { hasGlobalPermission } from '../../../helpers/users';
import { AppState } from '../../../types/appstate';
import { ComponentQualifier } from '../../../types/component';
import { Permissions } from '../../../types/permissions';
import { AppState, LoggedInUser } from '../../../types/types';
import { LoggedInUser } from '../../../types/types';

export interface ApplicationCreationProps {
appState: AppState;

+ 2
- 1
server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx Переглянути файл

@@ -29,7 +29,8 @@ import SearchBox from '../../components/controls/SearchBox';
import SelectLegacy from '../../components/controls/SelectLegacy';
import QualifierIcon from '../../components/icons/QualifierIcon';
import { translate } from '../../helpers/l10n';
import { AppState, Visibility } from '../../types/types';
import { AppState } from '../../types/appstate';
import { Visibility } from '../../types/types';
import BulkApplyTemplateModal from './BulkApplyTemplateModal';
import DeleteModal from './DeleteModal';


+ 2
- 7
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx Переглянути файл

@@ -27,14 +27,9 @@ import ModalButton from '../../../components/controls/ModalButton';
import { Alert } from '../../../components/ui/Alert';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { isDiffMetric } from '../../../helpers/measures';
import { AppState } from '../../../types/appstate';
import { MetricKey } from '../../../types/metrics';
import {
AppState,
Condition as ConditionType,
Dict,
Metric,
QualityGate
} from '../../../types/types';
import { Condition as ConditionType, Dict, Metric, QualityGate } from '../../../types/types';
import Condition from './Condition';
import ConditionModal from './ConditionModal';


+ 1
- 1
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap Переглянути файл

@@ -25,7 +25,7 @@ exports[`should render correctly: user 1`] = `
<div
className="display-flex-center permission-list-item padded"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right"
name="John Doe"
size={32}

+ 1
- 1
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap Переглянути файл

@@ -334,7 +334,7 @@ exports[`should render options correctly: group 1`] = `

exports[`should render options correctly: user 1`] = `
<React.Fragment>
<Connect(Avatar)
<withAppStateContext(Avatar)
hash="A"
name="name"
size={16}

+ 1
- 1
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsUser-test.tsx.snap Переглянути файл

@@ -8,7 +8,7 @@ exports[`renders 1`] = `
className="pull-right spacer-top spacer-left spacer-right button-small"
onClick={[Function]}
/>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="pull-left spacer-right"
name="Luke Skywalker"
size={32}

+ 13
- 13
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap Переглянути файл

@@ -10,7 +10,7 @@ exports[`should render correctly: default 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Luke Skywalker"
size={20}
@@ -49,7 +49,7 @@ exports[`should render correctly: default 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Luke Skywalker"
size={20}
@@ -88,7 +88,7 @@ exports[`should render correctly: default 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}
@@ -130,7 +130,7 @@ exports[`should render correctly: default 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}
@@ -172,7 +172,7 @@ exports[`should render correctly: default 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}
@@ -255,7 +255,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Luke Skywalker"
size={20}
@@ -294,7 +294,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Luke Skywalker"
size={20}
@@ -333,7 +333,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}
@@ -375,7 +375,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}
@@ -417,7 +417,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}
@@ -459,7 +459,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="john.doe"
size={20}
@@ -501,7 +501,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}
@@ -592,7 +592,7 @@ exports[`should render correctly: show full list 1`] = `
<div
className="display-flex-center"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="John Doe"
size={20}

+ 2
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/__snapshots__/AssigneeSelectionRenderer-test.tsx.snap Переглянути файл

@@ -71,7 +71,7 @@ exports[`should render correctly: open with results 1`] = `
key="john.doe"
onClick={[Function]}
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right"
name="John Doe"
size={16}
@@ -83,7 +83,7 @@ exports[`should render correctly: open with results 1`] = `
key="highlighted"
onClick={[Function]}
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right"
name="John Doe"
size={16}

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx Переглянути файл

@@ -23,7 +23,8 @@ import * as React from 'react';
import { IndexLink } from 'react-router';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../../helpers/urls';
import { AppState, Component } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { Component } from '../../../types/types';
import { getCategoryName } from '../utils';
import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
import CATEGORY_OVERRIDES from './CategoryOverrides';

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx Переглянути файл

@@ -34,8 +34,9 @@ import {
AlmSettingsBindingStatus,
AlmSettingsBindingStatusType
} from '../../../../types/alm-settings';
import { AppState } from '../../../../types/appstate';
import { ExtendedSettingDefinition } from '../../../../types/settings';
import { AppState, Dict } from '../../../../types/types';
import { Dict } from '../../../../types/types';
import AlmIntegrationRenderer from './AlmIntegrationRenderer';

interface Props extends Pick<WithRouterProps, 'location' | 'router'> {

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/CreationTooltip.tsx Переглянути файл

@@ -24,8 +24,8 @@ import Tooltip from '../../../../components/controls/Tooltip';
import { getEdition, getEditionUrl } from '../../../../helpers/editions';
import { translate } from '../../../../helpers/l10n';
import { AlmKeys } from '../../../../types/alm-settings';
import { AppState } from '../../../../types/appstate';
import { EditionKey } from '../../../../types/editions';
import { AppState } from '../../../../types/types';

export interface CreationTooltipProps {
alm: AlmKeys;

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx Переглянути файл

@@ -32,8 +32,9 @@ import {
AlmSettingsInstance,
ProjectAlmBindingResponse
} from '../../../../types/alm-settings';
import { AppState } from '../../../../types/appstate';
import { EditionKey } from '../../../../types/editions';
import { AppState, Dict } from '../../../../types/types';
import { Dict } from '../../../../types/types';

export interface AlmSpecificFormProps {
alm: AlmKeys;

+ 0
- 55
server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts Переглянути файл

@@ -1,55 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { fetchValues, setValues } from '../actions';

jest.mock('../../../../api/settings', () => {
const { mockSettingValue } = jest.requireActual('../../../../helpers/mocks/settings');
return {
getValues: jest.fn().mockResolvedValue([mockSettingValue()])
};
});

it('should setValues correctly', () => {
const dispatch = jest.fn();
setValues(['test'], [{ key: 'test', value: 'foo' }])(dispatch);
expect(dispatch).toHaveBeenCalledWith({
component: undefined,
settings: [
{
key: 'test',
value: 'foo'
}
],
type: 'RECEIVE_VALUES',
updateKeys: ['test']
});
});

it('should fetchValue correclty', async () => {
const dispatch = jest.fn();
await fetchValues(['test'], 'foo')(dispatch);
expect(dispatch).toHaveBeenCalledWith({
component: 'foo',
settings: [{ key: 'test' }],
type: 'RECEIVE_VALUES',
updateKeys: ['test']
});
expect(dispatch).toHaveBeenCalledWith({ type: 'CLOSE_ALL_GLOBAL_MESSAGES' });
});

+ 0
- 39
server/sonar-web/src/main/js/apps/settings/store/actions.ts Переглянути файл

@@ -1,39 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { getValues } from '../../../api/settings';
import { closeAllGlobalMessages } from '../../../store/globalMessages';
import { receiveValues } from './values';

export function fetchValues(keys: string[], component?: string) {
return (dispatch: Dispatch) =>
getValues({ keys: keys.join(), component }).then(settings => {
dispatch(receiveValues(keys, settings, component));
dispatch(closeAllGlobalMessages());
});
}

export function setValues(
keys: string[],
settings: Array<{ key: string; value?: string }>,
component?: string
) {
return (dispatch: Dispatch) => dispatch(receiveValues(keys, settings, component));
}

+ 0
- 78
server/sonar-web/src/main/js/apps/settings/store/values.ts Переглянути файл

@@ -1,78 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { keyBy, omit } from 'lodash';
import { combineReducers } from 'redux';
import { ActionType } from '../../../store/utils/actions';
import { SettingValue } from '../../../types/settings';
import { Dict } from '../../../types/types';

enum Actions {
receiveValues = 'RECEIVE_VALUES'
}

type Action = ActionType<typeof receiveValues, Actions.receiveValues>;

type SettingsState = Dict<SettingValue>;

export interface State {
components: Dict<SettingsState>;
global: SettingsState;
}

export function receiveValues(
updateKeys: string[],
settings: Array<{ key: string; value?: string }>,
component?: string
) {
return { type: Actions.receiveValues, updateKeys, settings, component };
}

function components(state: State['components'] = {}, action: Action) {
const { component: key } = action;
if (!key) {
return state;
}
if (action.type === Actions.receiveValues) {
const settingsByKey = keyBy(action.settings, 'key');
return { ...state, [key]: { ...omit(state[key] || {}, action.updateKeys), ...settingsByKey } };
}
return state;
}

function global(state: State['components'] = {}, action: Action) {
if (action.type === Actions.receiveValues) {
if (action.component) {
return state;
}
const settingsByKey = keyBy(action.settings, 'key');
return { ...omit(state, action.updateKeys), ...settingsByKey };
}

return state;
}

export default combineReducers({ components, global });

export function getValue(state: State, key: string, component?: string): SettingValue | undefined {
if (component) {
return state.components[component] && state.components[component][key];
}
return state.global[key];
}

+ 1
- 1
server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx Переглянути файл

@@ -23,7 +23,7 @@ import { ClipboardButton } from '../../../components/controls/clipboard';
import { Alert } from '../../../components/ui/Alert';
import { toShortNotSoISOString } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import PageActions from './PageActions';

export interface Props {

+ 2
- 2
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserListItem-test.tsx.snap Переглянути файл

@@ -5,7 +5,7 @@ exports[`should render correctly 1`] = `
<td
className="thin nowrap text-middle"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
name="One"
size={36}
/>
@@ -92,7 +92,7 @@ exports[`should render correctly without last connection date 1`] = `
<td
className="thin nowrap text-middle"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
name="One"
size={36}
/>

+ 4
- 4
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.tsx.snap Переглянути файл

@@ -88,7 +88,7 @@ exports[`UsersSelectSearchOption should render correctly with email instead of h
role="listitem"
title="Administrator"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
name="Administrator"
size={16}
/>
@@ -113,7 +113,7 @@ exports[`UsersSelectSearchOption should render correctly without all parameters
role="listitem"
title="Administrator"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
hash="7daf6c79d4802916d83f6266e24850af"
name="Administrator"
size={16}
@@ -139,7 +139,7 @@ exports[`UsersSelectSearchValue should render correctly with a user 1`] = `
<div
className="Select-value-label"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
hash="7daf6c79d4802916d83f6266e24850af"
name="Administrator"
size={16}
@@ -166,7 +166,7 @@ exports[`UsersSelectSearchValue should render correctly with email instead of ha
<div
className="Select-value-label"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
name="Administrator"
size={16}
/>

+ 2
- 1
server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx Переглянути файл

@@ -27,10 +27,11 @@ import withAppStateContext from '../../app/components/app-state/withAppStateCont
import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { isLoggedIn } from '../../helpers/users';
import { AppState } from '../../types/appstate';
import { Branch } from '../../types/branch-like';
import { ComponentQualifier } from '../../types/component';
import { ComponentReportStatus } from '../../types/component-report';
import { AppState, Component, CurrentUser } from '../../types/types';
import { Component, CurrentUser } from '../../types/types';
import { withCurrentUser } from '../hoc/withCurrentUser';
import ComponentReportActionsRenderer from './ComponentReportActionsRenderer';


+ 1
- 1
server/sonar-web/src/main/js/components/docs/DocLink.tsx Переглянути файл

@@ -22,7 +22,7 @@ import { Link } from 'react-router';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import DetachIcon from '../../components/icons/DetachIcon';
import { isSonarCloud } from '../../helpers/system';
import { AppState } from '../../types/types';
import { AppState } from '../../types/appstate';

interface OwnProps {
appState: AppState;

+ 3
- 3
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap Переглянути файл

@@ -32,7 +32,7 @@ exports[`should open the popup when the button is clicked 2`] = `
<span
className="text-top"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name=""
@@ -108,7 +108,7 @@ exports[`should render with the action 1`] = `
<span
className="text-top"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name=""
@@ -133,7 +133,7 @@ exports[`should render without the action when the correct rights are missing 1`
<span
className="text-top"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name=""

+ 4
- 4
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.tsx.snap Переглянути файл

@@ -20,7 +20,7 @@ exports[`should open the right popups when the buttons are clicked 3`] = `
className="issue-comment-author"
title="John Doe"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
@@ -118,7 +118,7 @@ exports[`should render correctly a comment that is not updatable 1`] = `
className="issue-comment-author"
title="John Doe"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
@@ -160,7 +160,7 @@ exports[`should render correctly a comment that is updatable 1`] = `
className="issue-comment-author"
title="John Doe"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
@@ -255,7 +255,7 @@ exports[`should render correctly a comment with a deleted author 1`] = `
className="issue-comment-author"
title="user.x_deleted.john.doe"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="john.doe"

+ 2
- 2
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/ChangelogPopup-test.tsx.snap Переглянути файл

@@ -39,7 +39,7 @@ exports[`should render the changelog popup correctly 1`] = `
className="text-left text-top"
>
<p>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
@@ -104,7 +104,7 @@ exports[`should render the changelog popup when we have a deleted user 1`] = `
className="text-left text-top"
>
<p>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right"
name="john.doe"
size={16}

+ 1
- 1
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetAssigneePopup-test.tsx.snap Переглянути файл

@@ -33,7 +33,7 @@ exports[`should render correctly 1`] = `
item="luke"
key="luke"
>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right"
name="Skywalker"
size={16}

+ 2
- 2
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SimilarIssuesPopup-test.tsx.snap Переглянути файл

@@ -127,7 +127,7 @@ exports[`should render correctly when assigned 1`] = `
>
<span>
assigned_to
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-left little-spacer-right"
name="Luke Skywalker"
size={16}
@@ -143,7 +143,7 @@ exports[`should render correctly when assigned 2`] = `
>
<span>
assigned_to
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-left little-spacer-right"
name="luke"
size={16}

+ 3
- 2
server/sonar-web/src/main/js/components/measure/Measure.tsx Переглянути файл

@@ -22,7 +22,7 @@ import Tooltip from '../../components/controls/Tooltip';
import Level from '../../components/ui/Level';
import Rating from '../../components/ui/Rating';
import { formatMeasure } from '../../helpers/measures';
import { getRatingTooltip } from './utils';
import RatingTooltipContent from './RatingTooltipContent';

interface Props {
className?: string;
@@ -57,8 +57,9 @@ export default function Measure({
return <span className={className}>{formattedValue != null ? formattedValue : '–'}</span>;
}

const tooltip = getRatingTooltip(metricKey, Number(value));
const tooltip = <RatingTooltipContent metricKey={metricKey} value={value} />;
const rating = <Rating small={small} value={value} />;

if (tooltip) {
return (
<Tooltip overlay={tooltip}>

+ 94
- 0
server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx Переглянути файл

@@ -0,0 +1,94 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 * as React from 'react';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../helpers/measures';
import { AppState } from '../../types/appstate';
import { MetricKey } from '../../types/metrics';
import { GlobalSettingKeys } from '../../types/settings';
import { KNOWN_RATINGS } from './utils';

const RATING_GRID_SIZE = 4;
const DIFF_METRIC_PREFIX_LENGTH = 4;
const PERCENT_MULTIPLIER = 100;
const GRID_INDEX_OFFSET = 2; // Rating of 2 should get index 0 (threshold between 1 and 2)

export interface RatingTooltipContentProps {
appState: AppState;
metricKey: MetricKey | string;
value: number | string;
}

function getMaintainabilityGrid(ratingGridSetting: string) {
const numbers = ratingGridSetting
.split(',')
.map(s => parseFloat(s))
.filter(n => !isNaN(n));

return numbers.length === RATING_GRID_SIZE ? numbers : [0, 0, 0, 0];
}

export function RatingTooltipContent(props: RatingTooltipContentProps) {
const {
appState: { settings },
metricKey,
value
} = props;

const finalMetricKey = isDiffMetric(metricKey)
? metricKey.slice(DIFF_METRIC_PREFIX_LENGTH)
: metricKey;

if (!KNOWN_RATINGS.includes(finalMetricKey)) {
return null;
}

const rating = Number(value);
const ratingLetter = formatMeasure(value, 'RATING');

if (finalMetricKey !== 'sqale_rating' && finalMetricKey !== 'maintainability_rating') {
return <>{translate('metric', finalMetricKey, 'tooltip', ratingLetter)}</>;
}

const maintainabilityGrid = getMaintainabilityGrid(settings[GlobalSettingKeys.RatingGrid] ?? '');
const maintainabilityRatingThreshold =
maintainabilityGrid[Math.floor(rating) - GRID_INDEX_OFFSET];

return (
// Required to correctly satisfy the context typing
// eslint-disable-next-line react/jsx-no-useless-fragment
<>
{rating === 1
? translateWithParameters(
'metric.sqale_rating.tooltip.A',
formatMeasure(maintainabilityGrid[0] * PERCENT_MULTIPLIER, 'PERCENT')
)
: translateWithParameters(
'metric.sqale_rating.tooltip',
ratingLetter,
formatMeasure(maintainabilityRatingThreshold * PERCENT_MULTIPLIER, 'PERCENT')
)}
</>
);
}

export default withAppStateContext(RatingTooltipContent);

+ 1
- 13
server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx Переглянути файл

@@ -21,12 +21,6 @@ import { shallow } from 'enzyme';
import * as React from 'react';
import Measure from '../Measure';

jest.mock('../../../helpers/measures', () => {
const measures = jest.requireActual('../../../helpers/measures');
measures.getRatingTooltip = jest.fn(() => 'tooltip');
return measures;
});

it('renders trivial measure', () => {
expect(
shallow(<Measure metricKey="coverage" metricType="PERCENT" value="73.0" />)
@@ -45,18 +39,12 @@ it('renders LEVEL', () => {
).toMatchSnapshot();
});

it('renders known RATING', () => {
it('renders RATING', () => {
expect(
shallow(<Measure metricKey="sqale_rating" metricType="RATING" value="3" />)
).toMatchSnapshot();
});

it('renders unknown RATING', () => {
expect(
shallow(<Measure metricKey="foo_rating" metricType="RATING" value="4" />)
).toMatchSnapshot();
});

it('renders undefined measure', () => {
expect(
shallow(<Measure metricKey="foo" metricType="PERCENT" value={undefined} />)

+ 62
- 0
server/sonar-web/src/main/js/components/measure/__tests__/RatingTooltipContent-test.tsx Переглянути файл

@@ -0,0 +1,62 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import { mockAppState } from '../../../helpers/testMocks';
import { GlobalSettingKeys } from '../../../types/settings';
import { MetricKey } from '../../../types/metrics';
import { RatingTooltipContent, RatingTooltipContentProps } from '../RatingTooltipContent';

it('should render maintainability correctly', () => {
expect(shallowRender()).toMatchSnapshot('sqale rating');
expect(shallowRender({ value: 1 })).toMatchSnapshot('sqale rating A');
expect(shallowRender({ appState: mockAppState({ settings: {} }) })).toMatchSnapshot(
'sqale rating default grid'
);
expect(
shallowRender({
appState: mockAppState({ settings: { [GlobalSettingKeys.RatingGrid]: '0,0.1' } })
})
).toMatchSnapshot('sqale rating wrong grid');
});

it('should render other ratings correctly', () => {
expect(shallowRender({ metricKey: MetricKey.security_rating })).toMatchSnapshot(
'security rating'
);
expect(shallowRender({ metricKey: MetricKey.new_security_rating })).toMatchSnapshot(
'new security rating'
);
});

it('should ignore non-rating metrics', () => {
expect(shallowRender({ metricKey: MetricKey.code_smells }).type()).toBeNull();
});

function shallowRender(overrides: Partial<RatingTooltipContentProps> = {}) {
return shallow(
<RatingTooltipContent
appState={mockAppState({ settings: { [GlobalSettingKeys.RatingGrid]: '0.05,0.1,0.2,0.4' } })}
metricKey={MetricKey.sqale_rating}
value={2}
{...overrides}
/>
);
}

+ 7
- 8
server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap Переглянути файл

@@ -6,9 +6,14 @@ exports[`renders LEVEL 1`] = `
/>
`;

exports[`renders known RATING 1`] = `
exports[`renders RATING 1`] = `
<Tooltip
overlay="tooltip"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="sqale_rating"
value="3"
/>
}
>
<span>
<Rating
@@ -35,9 +40,3 @@ exports[`renders undefined measure 1`] = `
</span>
`;

exports[`renders unknown RATING 1`] = `
<Rating
value="4"
/>
`;

+ 37
- 0
server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/RatingTooltipContent-test.tsx.snap Переглянути файл

@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render maintainability correctly: sqale rating 1`] = `
<Fragment>
metric.sqale_rating.tooltip.B.5.0%
</Fragment>
`;

exports[`should render maintainability correctly: sqale rating A 1`] = `
<Fragment>
metric.sqale_rating.tooltip.A.5.0%
</Fragment>
`;

exports[`should render maintainability correctly: sqale rating default grid 1`] = `
<Fragment>
metric.sqale_rating.tooltip.B.0.0%
</Fragment>
`;

exports[`should render maintainability correctly: sqale rating wrong grid 1`] = `
<Fragment>
metric.sqale_rating.tooltip.B.0.0%
</Fragment>
`;

exports[`should render other ratings correctly: new security rating 1`] = `
<Fragment>
metric.security_rating.tooltip.B
</Fragment>
`;

exports[`should render other ratings correctly: security rating 1`] = `
<Fragment>
metric.security_rating.tooltip.B
</Fragment>
`;

+ 1
- 10
server/sonar-web/src/main/js/components/measure/utils.ts Переглянути файл

@@ -17,10 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getRatingTooltip as nextGetRatingTooltip, isDiffMetric } from '../../helpers/measures';
import { Dict, Measure, MeasureEnhanced, MeasureIntern, Metric } from '../../types/types';

const KNOWN_RATINGS = [
export const KNOWN_RATINGS = [
'sqale_rating',
'maintainability_rating', // Needed to provide the label for "new_maintainability_rating"
'reliability_rating',
@@ -39,11 +38,3 @@ export function enhanceMeasure(measure: Measure, metrics: Dict<Metric>): Measure
export function getLeakValue(measure: MeasureIntern | undefined): string | undefined {
return measure?.period?.value;
}

export function getRatingTooltip(metricKey: string, value: number): string | undefined {
const finalMetricKey = isDiffMetric(metricKey) ? metricKey.substr(4) : metricKey;
if (KNOWN_RATINGS.includes(finalMetricKey)) {
return nextGetRatingTooltip(finalMetricKey, value);
}
return undefined;
}

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/commands/PublishSteps.tsx Переглянути файл

@@ -25,7 +25,7 @@ import { Alert } from '../../../../components/ui/Alert';
import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n';
import { AlmKeys } from '../../../../types/alm-settings';
import { AppState } from '../../../../types/types';
import { AppState } from '../../../../types/appstate';
import SentenceWithHighlights from '../../components/SentenceWithHighlights';

export interface PublishStepsProps {

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/AnalysisCommand.tsx Переглянути файл

@@ -20,7 +20,8 @@
import { Dictionary } from 'lodash';
import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { AppState, Component } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { Component } from '../../../types/types';
import { CompilationInfo } from '../components/CompilationInfo';
import CreateYmlFile from '../components/CreateYmlFile';
import { BuildTools } from '../types';

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx Переглянути файл

@@ -22,7 +22,7 @@ import withAppStateContext from '../../../app/components/app-state/withAppStateC
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { AlmKeys } from '../../../types/alm-settings';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import SentenceWithHighlights from './SentenceWithHighlights';

export interface AllSetProps {

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx Переглянути файл

@@ -19,7 +19,8 @@
*/
import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { AppState, Component } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { Component } from '../../../types/types';
import { BuildTools } from '../types';
import CFamily from './commands/CFamily';
import DotNet from './commands/DotNet';

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx Переглянути файл

@@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { ClipboardIconButton } from '../../../components/controls/clipboard';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import FinishButton from '../components/FinishButton';
import GithubCFamilyExampleRepositories from '../components/GithubCFamilyExampleRepositories';
import Step from '../components/Step';

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx Переглянути файл

@@ -28,7 +28,8 @@ import {
AlmSettingsInstance,
ProjectAlmBindingResponse
} from '../../../types/alm-settings';
import { AppState, Component, CurrentUserSetting } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { Component, CurrentUserSetting } from '../../../types/types';
import AllSetStep from '../components/AllSetStep';
import JenkinsfileStep from './JenkinsfileStep';
import MultiBranchPipelineStep from './MultiBranchPipelineStep';

+ 29
- 27
server/sonar-web/src/main/js/components/ui/Avatar.tsx Переглянути файл

@@ -19,51 +19,53 @@
*/
import classNames from 'classnames';
import * as React from 'react';
import { connect } from 'react-redux';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import GenericAvatar from '../../components/ui/GenericAvatar';
import { getGlobalSettingValue, Store } from '../../store/rootReducer';
import { AppState } from '../../types/appstate';
import { GlobalSettingKeys } from '../../types/settings';

const GRAVATAR_SIZE_MULTIPLIER = 2;

interface Props {
appState: AppState;
className?: string;
enableGravatar: boolean;
gravatarServerUrl: string;
hash?: string;
name?: string;
size: number;
}

function Avatar(props: Props) {
if (!props.enableGravatar || !props.hash) {
if (!props.name) {
export function Avatar(props: Props) {
const {
appState: { settings },
className,
hash,
name,
size
} = props;

const enableGravatar = settings[GlobalSettingKeys.EnableGravatar] === 'true';

if (!enableGravatar || !hash) {
if (!name) {
return null;
}
return <GenericAvatar className={props.className} name={props.name} size={props.size} />;
return <GenericAvatar className={className} name={name} size={size} />;
}

const url = props.gravatarServerUrl
.replace('{EMAIL_MD5}', props.hash)
.replace('{SIZE}', String(props.size * 2));
const gravatarServerUrl = settings[GlobalSettingKeys.GravatarServerUrl] ?? '';
const url = gravatarServerUrl
.replace('{EMAIL_MD5}', hash)
.replace('{SIZE}', String(size * GRAVATAR_SIZE_MULTIPLIER));

return (
<img
alt={props.name}
className={classNames(props.className, 'rounded')}
height={props.size}
alt={name}
className={classNames(className, 'rounded')}
height={size}
src={url}
width={props.size}
width={size}
/>
);
}

const mapStateToProps = (state: Store) => {
const enableGravatar = getGlobalSettingValue(state, 'sonar.lf.enableGravatar');
const gravatarServerUrl = getGlobalSettingValue(state, 'sonar.lf.gravatarServerUrl');
return {
enableGravatar: Boolean(enableGravatar && enableGravatar.value === 'true'),
gravatarServerUrl: (gravatarServerUrl && gravatarServerUrl.value) || ''
};
};

export default connect(mapStateToProps)(Avatar);

export const unconnectedAvatar = Avatar;
export default withAppStateContext(Avatar);

+ 11
- 5
server/sonar-web/src/main/js/components/ui/__tests__/Avatar-test.tsx Переглянути файл

@@ -19,15 +19,21 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { unconnectedAvatar as Avatar } from '../Avatar';
import { mockAppState } from '../../../helpers/testMocks';
import { GlobalSettingKeys } from '../../../types/settings';
import { Avatar } from '../Avatar';

const gravatarServerUrl = 'http://example.com/{EMAIL_MD5}.jpg?s={SIZE}';

it('should be able to render with hash only', () => {
const avatar = shallow(
<Avatar
enableGravatar={true}
gravatarServerUrl={gravatarServerUrl}
appState={mockAppState({
settings: {
[GlobalSettingKeys.EnableGravatar]: 'true',
[GlobalSettingKeys.GravatarServerUrl]: gravatarServerUrl
}
})}
hash="7daf6c79d4802916d83f6266e24850af"
name="Foo"
size={30}
@@ -38,14 +44,14 @@ it('should be able to render with hash only', () => {

it('falls back to dummy avatar', () => {
const avatar = shallow(
<Avatar enableGravatar={false} gravatarServerUrl="" name="Foo Bar" size={30} />
<Avatar appState={mockAppState({ settings: {} })} name="Foo Bar" size={30} />
);
expect(avatar).toMatchSnapshot();
});

it('do not fail when name is missing', () => {
const avatar = shallow(
<Avatar enableGravatar={false} gravatarServerUrl="" name={undefined} size={30} />
<Avatar appState={mockAppState({ settings: {} })} name={undefined} size={30} />
);
expect(avatar.getElement()).toBeNull();
});

+ 1
- 1
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx Переглянути файл

@@ -21,9 +21,9 @@ import { filter, flatMap, isEmpty, negate } from 'lodash';
import * as React from 'react';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { EditionKey } from '../../types/editions';
import { SystemUpgrade } from '../../types/system';
import { AppState } from '../../types/types';
import { ResetButtonLink } from '../controls/buttons';
import Modal from '../controls/Modal';
import { Alert, AlertVariant } from '../ui/Alert';

+ 0
- 26
server/sonar-web/src/main/js/helpers/analytics.js Переглянути файл

@@ -1,26 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.
*/
// The body of the `gtm` function comes from Google Tag Manager docs; let's keep it like it was written.
// @ts-ignore
// prettier-ignore
// eslint-disable-next-line
const gtm = id => (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});const f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);}(window,document,'script','dataLayer',id));

module.exports = { gtm };

+ 0
- 62
server/sonar-web/src/main/js/helpers/measures.ts Переглянути файл

@@ -63,68 +63,6 @@ export function isDiffMetric(metricKey: MetricKey | string): boolean {
return metricKey.indexOf('new_') === 0;
}

function getRatingGrid(): string {
// workaround cyclic dependencies
const getStore = require('../app/utils/getStore').default;
const { getGlobalSettingValue } = require('../store/rootReducer');

const store = getStore();
const settingValue = getGlobalSettingValue(store.getState(), 'sonar.technicalDebt.ratingGrid');
return settingValue ? settingValue.value : '';
}

let maintainabilityRatingGrid: number[];

function getMaintainabilityRatingGrid(): number[] {
if (maintainabilityRatingGrid) {
return maintainabilityRatingGrid;
}

const str = getRatingGrid();
const numbers = str
.split(',')
.map(s => parseFloat(s))
.filter(n => !isNaN(n));

if (numbers.length === 4) {
maintainabilityRatingGrid = numbers;
} else {
maintainabilityRatingGrid = [0, 0, 0, 0];
}

return maintainabilityRatingGrid;
}

function getMaintainabilityRatingTooltip(rating: number): string {
const maintainabilityGrid = getMaintainabilityRatingGrid();
const maintainabilityRatingThreshold = maintainabilityGrid[Math.floor(rating) - 2];

if (rating < 2) {
return translateWithParameters(
'metric.sqale_rating.tooltip.A',
formatMeasure(maintainabilityGrid[0] * 100, 'PERCENT')
);
}

const ratingLetter = formatMeasure(rating, 'RATING');

return translateWithParameters(
'metric.sqale_rating.tooltip',
ratingLetter,
formatMeasure(maintainabilityRatingThreshold * 100, 'PERCENT')
);
}

export function getRatingTooltip(metricKey: MetricKey | string, value: number | string): string {
const ratingLetter = formatMeasure(value, 'RATING');

const finalMetricKey = isDiffMetric(metricKey) ? metricKey.substr(4) : metricKey;

return finalMetricKey === 'sqale_rating' || finalMetricKey === 'maintainability_rating'
? getMaintainabilityRatingTooltip(Number(value))
: translate('metric', finalMetricKey, 'tooltip', ratingLetter);
}

export function getDisplayMetrics(metrics: Metric[]) {
return metrics.filter(metric => !metric.hidden && !['DATA', 'DISTRIB'].includes(metric.type));
}

+ 0
- 0
server/sonar-web/src/main/js/helpers/testMocks.ts Переглянути файл


Деякі файли не було показано, через те що забагато файлів було змінено

Завантаження…
Відмінити
Зберегти