Przeglądaj źródła

SONAR-15992 Remove Global Settings from redux

tags/9.4.0.54424
Jeremy Davis 2 lat temu
rodzic
commit
accf42f839
100 zmienionych plików z 784 dodań i 718 usunięć
  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 Wyświetl plik

import { getSystemStatus, waitSystemUPStatus } from '../../api/system'; import { getSystemStatus, waitSystemUPStatus } from '../../api/system';
import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization'; import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
import { translate } from '../../helpers/l10n'; import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { PendingPluginResult } from '../../types/plugins'; 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 AdminContext, { defaultPendingPlugins, defaultSystemStatus } from './AdminContext';
import withAppStateContext from './app-state/withAppStateContext'; import withAppStateContext from './app-state/withAppStateContext';
import SettingsNav from './nav/settings/SettingsNav'; import SettingsNav from './nav/settings/SettingsNav';

+ 18
- 16
server/sonar-web/src/main/js/app/components/App.tsx Wyświetl plik

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux';
import { lazyLoadComponent } from '../../components/lazyLoadComponent'; 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'; import KeyboardShortcutsModal from './KeyboardShortcutsModal';


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


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


export class App extends React.PureComponent<Props> { export class App extends React.PureComponent<Props> {
}; };


renderPreconnectLink = () => { 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'); const parser = document.createElement('a');
parser.href = this.props.gravatarServerUrl;
parser.href = gravatarServerUrl;
if (parser.hostname !== window.location.hostname) { if (parser.hostname !== window.location.hostname) {
return <link href={parser.origin} rel="preconnect" />; return <link href={parser.origin} rel="preconnect" />;
} }
render() { render() {
return ( return (
<> <>
<PageTracker>{this.props.enableGravatar && this.renderPreconnectLink()}</PageTracker>
<PageTracker>{this.renderPreconnectLink()}</PageTracker>
{this.props.children} {this.props.children}
<KeyboardShortcutsModal /> <KeyboardShortcutsModal />
</> </>
} }
} }


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 Wyświetl plik

ProjectAlmBindingConfigurationErrors, ProjectAlmBindingConfigurationErrors,
ProjectAlmBindingResponse ProjectAlmBindingResponse
} from '../../types/alm-settings'; } from '../../types/alm-settings';
import { AppState } from '../../types/appstate';
import { BranchLike } from '../../types/branch-like'; import { BranchLike } from '../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../types/component'; import { ComponentQualifier, isPortfolioLike } from '../../types/component';
import { Task, TaskStatuses, TaskTypes, TaskWarning } from '../../types/tasks'; 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 handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
import withAppStateContext from './app-state/withAppStateContext'; import withAppStateContext from './app-state/withAppStateContext';
import ComponentContainerNotFound from './ComponentContainerNotFound'; import ComponentContainerNotFound from './ComponentContainerNotFound';

+ 1
- 1
server/sonar-web/src/main/js/app/components/GlobalFooter.tsx Wyświetl plik

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



+ 6
- 27
server/sonar-web/src/main/js/app/components/PageTracker.tsx Wyświetl plik

*/ */
import * as React from 'react'; import * as React from 'react';
import { Helmet } from 'react-helmet-async'; import { Helmet } from 'react-helmet-async';
import { connect } from 'react-redux';
import { Location, withRouter } from '../../components/hoc/withRouter'; import { Location, withRouter } from '../../components/hoc/withRouter';
import { gtm } from '../../helpers/analytics';
import { installScript } from '../../helpers/extensions'; import { installScript } from '../../helpers/extensions';
import { getWebAnalyticsPageHandlerFromCache } from '../../helpers/extensionsHandler'; import { getWebAnalyticsPageHandlerFromCache } from '../../helpers/extensionsHandler';
import { getInstance } from '../../helpers/system'; 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'; import withAppStateContext from './app-state/withAppStateContext';


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


state: State = {}; state: State = {};


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


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

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


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


if (webAnalyticsPageChange && locationChanged) { if (webAnalyticsPageChange && locationChanged) {
this.setState({ lastLocation: location.pathname }); this.setState({ lastLocation: location.pathname });
setTimeout(() => webAnalyticsPageChange(location.pathname), 500); 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() { render() {
const { trackingIdGTM, appState } = this.props;
const { appState } = this.props;


return ( return (
<Helmet <Helmet
defaultTitle={getInstance()} defaultTitle={getInstance()}
defer={false} defer={false}
onChangeClientState={
trackingIdGTM || appState.webAnalyticsJsPath ? this.trackPage : undefined
}>
onChangeClientState={appState.webAnalyticsJsPath ? this.trackPage : undefined}>
{this.props.children} {this.props.children}
</Helmet> </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 Wyświetl plik

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


const LicensePromptModal = lazyLoadComponent( const LicensePromptModal = lazyLoadComponent(

+ 20
- 24
server/sonar-web/src/main/js/app/components/__tests__/App-test.tsx Wyświetl plik

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux';
import { mockAppState } from '../../../helpers/testMocks';
import { App } from '../App'; 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', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default'); expect(shallowRender()).toMatchSnapshot('default');
expect( 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'); ).toMatchSnapshot('with gravatar');
}); });


expect(document.body.style.getPropertyValue('--sbw')).toBe('0px'); 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']> = {}) { 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 Wyświetl plik

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { gtm } from '../../../helpers/analytics';
import { installScript } from '../../../helpers/extensions'; import { installScript } from '../../../helpers/extensions';
import { getWebAnalyticsPageHandlerFromCache } from '../../../helpers/extensionsHandler'; import { getWebAnalyticsPageHandlerFromCache } from '../../../helpers/extensionsHandler';
import { mockAppState, mockLocation } from '../../../helpers/testMocks'; import { mockAppState, mockLocation } from '../../../helpers/testMocks';
getWebAnalyticsPageHandlerFromCache: jest.fn().mockReturnValue(undefined) getWebAnalyticsPageHandlerFromCache: jest.fn().mockReturnValue(undefined)
})); }));


jest.mock('../../../helpers/analytics', () => ({ gtm: jest.fn() }));
beforeAll(() => { beforeAll(() => {
jest.useFakeTimers(); jest.useFakeTimers();
}); });
const wrapper = shallowRender(); const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
expect(installScript).not.toHaveBeenCalled(); expect(installScript).not.toHaveBeenCalled();
expect(gtm).not.toHaveBeenCalled();
}); });


it('should work for WebAnalytics plugin', () => { it('should work for WebAnalytics plugin', () => {
expect(pageChange).toHaveBeenCalledWith('/path'); 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']> = {}) { function shallowRender(props: Partial<PageTracker['props']> = {}) {
return shallow<PageTracker>( return shallow<PageTracker>(
<PageTracker appState={mockAppState()} location={mockLocation()} {...props} /> <PageTracker appState={mockAppState()} location={mockLocation()} {...props} />

+ 1
- 1
server/sonar-web/src/main/js/app/components/app-state/AppStateContext.tsx Wyświetl plik

*/ */


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


const defaultAppState = { const defaultAppState = {
authenticationError: false, authenticationError: false,

+ 2
- 15
server/sonar-web/src/main/js/app/components/app-state/AppStateContextProvider.tsx Wyświetl plik

*/ */


import * as React from 'react'; 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'; import { AppStateContext } from './AppStateContext';


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


export function AppStateContextProvider({
setValues,
export default function AppStateContextProvider({
appState, appState,
children children
}: React.PropsWithChildren<AppStateContextProviderProps>) { }: 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>; 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 Wyświetl plik

import * as React from 'react'; import * as React from 'react';
import { mockAppState } from '../../../../helpers/testMocks'; import { mockAppState } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils'; import { waitAndUpdate } from '../../../../helpers/testUtils';
import { AppStateContextProvider, AppStateContextProviderProps } from '../AppStateContextProvider';
import AppStateContextProvider, { AppStateContextProviderProps } from '../AppStateContextProvider';


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

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


function render(override?: Partial<AppStateContextProviderProps>) { 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 Wyświetl plik

// 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 Wyświetl plik

import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockAppState } from '../../../../helpers/testMocks'; import { mockAppState } from '../../../../helpers/testMocks';
import { AppState } from '../../../../types/types';
import { AppState } from '../../../../types/appstate';
import withAppStateContext from '../withAppStateContext'; import withAppStateContext from '../withAppStateContext';


const appState = mockAppState(); const appState = mockAppState();

+ 1
- 1
server/sonar-web/src/main/js/app/components/app-state/withAppStateContext.tsx Wyświetl plik



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


export interface WithAppStateContextProps { export interface WithAppStateContextProps {

+ 2
- 1
server/sonar-web/src/main/js/app/components/extensions/Extension.tsx Wyświetl plik

import { getBaseUrl } from '../../../helpers/system'; import { getBaseUrl } from '../../../helpers/system';
import { addGlobalErrorMessage } from '../../../store/globalMessages'; import { addGlobalErrorMessage } from '../../../store/globalMessages';
import { getCurrentUser, Store } from '../../../store/rootReducer'; import { getCurrentUser, Store } from '../../../store/rootReducer';
import { AppState } from '../../../types/appstate';
import { ExtensionStartMethod } from '../../../types/extension'; 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 * as theme from '../../theme';
import getStore from '../../utils/getStore'; import getStore from '../../utils/getStore';
import withAppStateContext from '../app-state/withAppStateContext'; import withAppStateContext from '../app-state/withAppStateContext';

+ 1
- 1
server/sonar-web/src/main/js/app/components/extensions/GlobalPageExtension.tsx Wyświetl plik

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

+ 2
- 0
server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts Wyświetl plik

import DateFromNow from '../../../components/intl/DateFromNow'; import DateFromNow from '../../../components/intl/DateFromNow';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import Measure from '../../../components/measure/Measure'; import Measure from '../../../components/measure/Measure';
import RatingTooltipContent from '../../../components/measure/RatingTooltipContent';
import { Alert } from '../../../components/ui/Alert'; import { Alert } from '../../../components/ui/Alert';
import CoverageRating from '../../../components/ui/CoverageRating'; import CoverageRating from '../../../components/ui/CoverageRating';
import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import DeferredSpinner from '../../../components/ui/DeferredSpinner';
Radio, Radio,
RadioToggle, RadioToggle,
Rating, Rating,
RatingTooltipContent,
ReloadButton, ReloadButton,
ResetButtonLink, ResetButtonLink,
SearchBox, SearchBox,

+ 1
- 1
server/sonar-web/src/main/js/app/components/indexation/IndexationContextProvider.tsx Wyświetl plik

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

+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavLicenseNotif.tsx Wyświetl plik

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


interface Props { interface Props {

+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx Wyświetl plik

import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like'; import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like';
import { hasMessage, translate, translateWithParameters } from '../../../../helpers/l10n'; import { hasMessage, translate, translateWithParameters } from '../../../../helpers/l10n';
import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls'; import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls';
import { AppState } from '../../../../types/appstate';
import { BranchLike, BranchParameters } from '../../../../types/branch-like'; import { BranchLike, BranchParameters } from '../../../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component'; 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 withAppStateContext from '../../app-state/withAppStateContext';
import './Menu.css'; import './Menu.css';



+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx Wyświetl plik

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

+ 10
- 16
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavBranding.tsx Wyświetl plik

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { translate } from '../../../../helpers/l10n'; import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system'; 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 title = translate('layout.sonar.slogan');
const url = customLogoUrl || `${getBaseUrl()}/images/logo.svg?v=6.6`; const url = customLogoUrl || `${getBaseUrl()}/images/logo.svg?v=6.6`;
const width = customLogoUrl ? customLogoWidth || 100 : 83; const width = customLogoUrl ? customLogoWidth || 100 : 83;
); );
} }


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 Wyświetl plik

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


interface Props { interface Props {

+ 50
- 0
server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavBranding-test.tsx Wyświetl plik

/*
* 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 Wyświetl plik

height={48} height={48}
id="global-navigation" id="global-navigation"
> >
<Connect(GlobalNavBranding) />
<withAppStateContext(GlobalNavBranding) />
<withAppStateContext(GlobalNavMenu) <withAppStateContext(GlobalNavMenu)
currentUser={ currentUser={
Object { Object {
height={48} height={48}
id="global-navigation" id="global-navigation"
> >
<Connect(GlobalNavBranding) />
<withAppStateContext(GlobalNavBranding) />
<withAppStateContext(GlobalNavMenu) <withAppStateContext(GlobalNavMenu)
currentUser={ currentUser={
Object { Object {

+ 52
- 0
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavBranding-test.tsx.snap Wyświetl plik

// 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 Wyświetl plik

href="#" href="#"
title="Skywalker" title="Skywalker"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
name="Skywalker" name="Skywalker"
size={32} size={32}
/> />

+ 2
- 1
server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx Wyświetl plik

import { sortUpgrades, UpdateUseCase } from '../../../components/upgrade/utils'; import { sortUpgrades, UpdateUseCase } from '../../../components/upgrade/utils';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users'; import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users';
import { AppState } from '../../../types/appstate';
import { Permissions } from '../../../types/permissions'; import { Permissions } from '../../../types/permissions';
import { SystemUpgrade } from '../../../types/system'; 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 withAppStateContext from '../app-state/withAppStateContext';
import './UpdateNotification.css'; import './UpdateNotification.css';



+ 1
- 1
server/sonar-web/src/main/js/app/index.ts Wyświetl plik

import { loadL10nBundle } from '../helpers/l10n'; import { loadL10nBundle } from '../helpers/l10n';
import { parseJSON, request } from '../helpers/request'; import { parseJSON, request } from '../helpers/request';
import { getBaseUrl, getSystemStatus } from '../helpers/system'; import { getBaseUrl, getSystemStatus } from '../helpers/system';
import { AppState } from '../types/types';
import { AppState } from '../types/appstate';
import './styles/sonar.ts'; import './styles/sonar.ts';


installWebAnalyticsHandler(); installWebAnalyticsHandler();

+ 2
- 1
server/sonar-web/src/main/js/app/utils/startReactApp.tsx Wyświetl plik

import withIndexationGuard from '../../components/hoc/withIndexationGuard'; import withIndexationGuard from '../../components/hoc/withIndexationGuard';
import { lazyLoadComponent } from '../../components/lazyLoadComponent'; import { lazyLoadComponent } from '../../components/lazyLoadComponent';
import getHistory from '../../helpers/getHistory'; 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 App from '../components/App';
import AppStateContextProvider from '../components/app-state/AppStateContextProvider'; import AppStateContextProvider from '../components/app-state/AppStateContextProvider';
import GlobalContainer from '../components/GlobalContainer'; import GlobalContainer from '../components/GlobalContainer';

+ 28
- 40
server/sonar-web/src/main/js/apps/audit-logs/components/AuditApp.tsx Wyświetl plik

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; 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 { AdminPageExtension } from '../../../types/extension';
import { SettingsKey } from '../../../types/settings';
import { Extension } from '../../../types/types'; import { Extension } from '../../../types/types';
import { fetchValues } from '../../settings/store/actions';
import '../style.css'; import '../style.css';
import { HousekeepingPolicy, RangeOption } from '../utils'; import { HousekeepingPolicy, RangeOption } from '../utils';
import AuditAppRenderer from './AuditAppRenderer'; import AuditAppRenderer from './AuditAppRenderer';


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


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


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

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


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

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


componentDidUpdate(prevProps: Props) { 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 }) => handleDateSelection = (dateRange: { from?: Date; to?: Date }) =>
this.setState({ dateRange, downloadStarted: false, selection: RangeOption.Custom }); this.setState({ dateRange, downloadStarted: false, selection: RangeOption.Custom });


}; };


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

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


handleDateSelection={this.handleDateSelection} handleDateSelection={this.handleDateSelection}
handleOptionSelection={this.handleOptionSelection} handleOptionSelection={this.handleOptionSelection}
handleStartDownload={this.handleStartDownload} 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 Wyświetl plik

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


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

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

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


it('should do nothing if governance is not available', async () => { 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); await waitAndUpdate(wrapper);


expect(wrapper.type()).toBeNull(); 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); await waitAndUpdate(wrapper);
expect(fetchValues).toBeCalled();

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


it('should handle date selection', () => { it('should handle date selection', () => {
expect(wrapper.state().dateRange).toBeUndefined(); 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']> = {}) { function shallowRender(props: Partial<AuditApp['props']> = {}) {
return shallow<AuditApp>( return shallow<AuditApp>(
<AuditApp <AuditApp
auditHousekeepingPolicy={HousekeepingPolicy.Monthly}
fetchValues={jest.fn()}
adminPages={[{ key: AdminPageExtension.GovernanceConsole, name: 'name' }]} adminPages={[{ key: AdminPageExtension.GovernanceConsole, name: 'name' }]}
{...props} {...props}
/> />

+ 1
- 1
server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx Wyświetl plik

import ConfirmButton from '../../../components/controls/ConfirmButton'; import ConfirmButton from '../../../components/controls/ConfirmButton';
import Tooltip from '../../../components/controls/Tooltip'; import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';


export interface Props { export interface Props {
appState: AppState; appState: AppState;

+ 1
- 1
server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordApp.tsx Wyświetl plik

import { changePassword } from '../../api/users'; import { changePassword } from '../../api/users';
import withAppStateContext from '../../app/components/app-state/withAppStateContext'; import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { Location, withRouter } from '../../components/hoc/withRouter'; import { Location, withRouter } from '../../components/hoc/withRouter';
import { AppState } from '../../types/types';
import { AppState } from '../../types/appstate';
import ChangeAdminPasswordAppRenderer from './ChangeAdminPasswordAppRenderer'; import ChangeAdminPasswordAppRenderer from './ChangeAdminPasswordAppRenderer';
import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from './constants'; import { DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_PASSWORD } from './constants';



+ 2
- 1
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx Wyświetl plik

import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures'; import { formatMeasure } from '../../../helpers/measures';
import { getIssuesUrl } from '../../../helpers/urls'; import { getIssuesUrl } from '../../../helpers/urls';
import { AppState, RuleDetails } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { RuleDetails } from '../../../types/types';


interface Props { interface Props {
appState: AppState; appState: AppState;

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx Wyświetl plik

import { translate, translateWithParameters } from '../../../helpers/l10n'; import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system'; import { getBaseUrl } from '../../../helpers/system';
import { AlmKeys } from '../../../types/alm-settings'; import { AlmKeys } from '../../../types/alm-settings';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import { CreateProjectModes } from './types'; import { CreateProjectModes } from './types';


export interface CreateProjectModeSelectionProps { export interface CreateProjectModeSelectionProps {

+ 2
- 1
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx Wyświetl plik

import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls'; import { getProjectUrl } from '../../../helpers/urls';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; 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 AlmBindingDefinitionForm from '../../settings/components/almIntegration/AlmBindingDefinitionForm';
import AzureProjectCreate from './AzureProjectCreate'; import AzureProjectCreate from './AzureProjectCreate';
import BitbucketCloudProjectCreate from './BitbucketCloudProjectCreate'; import BitbucketCloudProjectCreate from './BitbucketCloudProjectCreate';

+ 1
- 1
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesApp-test.tsx.snap Wyświetl plik

displayReset={true} displayReset={true}
onReset={[Function]} onReset={[Function]}
/> />
<Connect(Sidebar)
<withAppStateContext(Sidebar)
component={ component={
Object { Object {
"breadcrumbs": Array [], "breadcrumbs": Array [],

+ 11
- 16
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx Wyświetl plik

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; 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 { isBranch, isPullRequest } from '../../../helpers/branch-like';
import { getGlobalSettingValue, Store } from '../../../store/rootReducer';
import { AppState } from '../../../types/appstate';
import { BranchLike } from '../../../types/branch-like'; import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier, isApplication, isPortfolioLike } from '../../../types/component'; import { ComponentQualifier, isApplication, isPortfolioLike } from '../../../types/component';
import { import {
ReferencedLanguage, ReferencedLanguage,
ReferencedRule ReferencedRule
} from '../../../types/issues'; } from '../../../types/issues';
import { GlobalSettingKeys } from '../../../types/settings';
import { Component, Dict, UserBase } from '../../../types/types'; import { Component, Dict, UserBase } from '../../../types/types';
import { Query } from '../utils'; import { Query } from '../utils';
import AssigneeFacet from './AssigneeFacet'; import AssigneeFacet from './AssigneeFacet';
import TypeFacet from './TypeFacet'; import TypeFacet from './TypeFacet';


export interface Props { export interface Props {
appState: AppState;
branchLike?: BranchLike; branchLike?: BranchLike;
component: Component | undefined; component: Component | undefined;
createdAfterIncludesTime: boolean; createdAfterIncludesTime: boolean;
referencedLanguages: Dict<ReferencedLanguage>; referencedLanguages: Dict<ReferencedLanguage>;
referencedRules: Dict<ReferencedRule>; referencedRules: Dict<ReferencedRule>;
referencedUsers: Dict<UserBase>; referencedUsers: Dict<UserBase>;
disableDeveloperAggregatedInfo: boolean;
} }


export class Sidebar extends React.PureComponent<Props> { export class Sidebar extends React.PureComponent<Props> {


render() { render() {
const { const {
appState: { settings },
component, component,
createdAfterIncludesTime, createdAfterIncludesTime,
facets, facets,
branchLike branchLike
} = this.props; } = this.props;


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

const branch = const branch =
(isBranch(branchLike) && branchLike.name) || (isBranch(branchLike) && branchLike.name) ||
(isPullRequest(branchLike) && branchLike.branch) || (isPullRequest(branchLike) && branchLike.branch) ||
/> />
)} )}
{this.renderComponentFacets()} {this.renderComponentFacets()}
{!this.props.myIssues && !this.props.disableDeveloperAggregatedInfo && (
{!this.props.myIssues && !disableDeveloperAggregatedInfo && (
<AssigneeFacet <AssigneeFacet
assigned={query.assigned} assigned={query.assigned}
assignees={query.assignees} assignees={query.assignees}
stats={facets.assignees} stats={facets.assignees}
/> />
)} )}
{displayAuthorFacet && !this.props.disableDeveloperAggregatedInfo && (
{displayAuthorFacet && !disableDeveloperAggregatedInfo && (
<AuthorFacet <AuthorFacet
author={query.author} author={query.author}
component={component} component={component}
} }
} }


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 Wyświetl plik

import { flatten } from 'lodash'; import { flatten } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component'; 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 { ComponentQualifier } from '../../../../types/component';
import { Query } from '../../utils'; 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', () => { it('should render facets for global page', () => {
expect(renderSidebar()).toMatchSnapshot(); expect(renderSidebar()).toMatchSnapshot();
}); });


it('should not render developer nominative facets when asked not to', () => { 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']>) => { const renderSidebar = (props?: Partial<Sidebar['props']>) => {
mapChildren( mapChildren(
shallow<Sidebar>( shallow<Sidebar>(
<Sidebar <Sidebar
appState={mockAppState({
settings: { [GlobalSettingKeys.DeveloperAggregatedInfoDisabled]: 'false' }
})}
component={undefined} component={undefined}
createdAfterIncludesTime={false} createdAfterIncludesTime={false}
facets={{}} facets={{}}
referencedLanguages={{}} referencedLanguages={{}}
referencedRules={{}} referencedRules={{}}
referencedUsers={{}} referencedUsers={{}}
disableDeveloperAggregatedInfo={false}
{...props} {...props}
/> />
) )

+ 4
- 4
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.tsx.snap Wyświetl plik



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


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


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


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

server/sonar-web/src/main/js/apps/marketplace/AppContainer.tsx → server/sonar-web/src/main/js/apps/marketplace/MarketplaceAppContainer.tsx Wyświetl plik

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux';
import AdminContext from '../../app/components/AdminContext'; import AdminContext from '../../app/components/AdminContext';
import withAppStateContext from '../../app/components/app-state/withAppStateContext'; 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 { 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'; import App from './App';


interface OwnProps {
export interface MarketplaceAppContainerProps {
location: { pathname: string; query: RawQuery }; location: { pathname: string; query: RawQuery };
appState: AppState; 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 = { 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 ( return (
); );
} }


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 Wyświetl plik

/*
* 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 Wyświetl plik

/*
* 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 Wyświetl plik

// 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 Wyświetl plik



const routes = [ 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 Wyświetl plik

import { lazyLoadComponent } from '../../../components/lazyLoadComponent'; import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
import { isPullRequest } from '../../../helpers/branch-like'; import { isPullRequest } from '../../../helpers/branch-like';
import { ProjectAlmBindingResponse } from '../../../types/alm-settings'; import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
import { AppState } from '../../../types/appstate';
import { BranchLike } from '../../../types/branch-like'; import { BranchLike } from '../../../types/branch-like';
import { isPortfolioLike } from '../../../types/component'; import { isPortfolioLike } from '../../../types/component';
import { AppState, Component } from '../../../types/types';
import { Component } from '../../../types/types';
import BranchOverview from '../branches/BranchOverview'; import BranchOverview from '../branches/BranchOverview';


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

+ 3
- 3
server/sonar-web/src/main/js/apps/overview/components/IssueRating.tsx Wyświetl plik

*/ */
import * as React from 'react'; import * as React from 'react';
import Tooltip from '../../../components/controls/Tooltip'; 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 DrilldownLink from '../../../components/shared/DrilldownLink';
import Rating from '../../../components/ui/Rating'; import Rating from '../../../components/ui/Rating';
import { findMeasure } from '../../../helpers/measures'; import { findMeasure } from '../../../helpers/measures';
} }


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


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

+ 36
- 6
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueRating-test.tsx.snap Wyświetl plik

metric_domain.Reliability metric_domain.Reliability
</span> </span>
<Tooltip <Tooltip
overlay="metric.reliability_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="reliability_rating"
value="1.0"
/>
}
> >
<span> <span>
<DrilldownLink <DrilldownLink
metric_domain.Reliability metric_domain.Reliability
</span> </span>
<Tooltip <Tooltip
overlay="metric.reliability_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="new_reliability_rating"
value="1.0"
/>
}
> >
<span> <span>
<DrilldownLink <DrilldownLink
metric_domain.Maintainability metric_domain.Maintainability
</span> </span>
<Tooltip <Tooltip
overlay="metric.sqale_rating.tooltip.A.0.0%"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="sqale_rating"
value="1.0"
/>
}
> >
<span> <span>
<DrilldownLink <DrilldownLink
metric_domain.Maintainability metric_domain.Maintainability
</span> </span>
<Tooltip <Tooltip
overlay="metric.sqale_rating.tooltip.A.0.0%"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="new_maintainability_rating"
value="1.0"
/>
}
> >
<span> <span>
<DrilldownLink <DrilldownLink
metric_domain.Security metric_domain.Security
</span> </span>
<Tooltip <Tooltip
overlay="metric.security_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="security_rating"
value="1.0"
/>
}
> >
<span> <span>
<DrilldownLink <DrilldownLink
metric_domain.Security metric_domain.Security
</span> </span>
<Tooltip <Tooltip
overlay="metric.security_rating.tooltip.A"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="new_security_rating"
value="1.0"
/>
}
> >
<span> <span>
<DrilldownLink <DrilldownLink

+ 2
- 1
server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx Wyświetl plik

import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n'; 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 '../../permissions/styles.css';
import { mergeDefaultsToTemplates, mergePermissionsToTemplates, sortPermissions } from '../utils'; import { mergeDefaultsToTemplates, mergePermissionsToTemplates, sortPermissions } from '../utils';
import Home from './Home'; import Home from './Home';

+ 2
- 1
server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx Wyświetl plik

import * as React from 'react'; import * as React from 'react';
import withAppStateContext from '../../../../app/components/app-state/withAppStateContext'; import withAppStateContext from '../../../../app/components/app-state/withAppStateContext';
import ListFooter from '../../../../components/controls/ListFooter'; import ListFooter from '../../../../components/controls/ListFooter';
import { AppState } from '../../../../types/appstate';
import { ComponentQualifier } from '../../../../types/component'; 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 HoldersList from '../../shared/components/HoldersList';
import SearchForm from '../../shared/components/SearchForm'; import SearchForm from '../../shared/components/SearchForm';
import { import {

+ 1
- 1
server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/UserHolder-test.tsx.snap Wyświetl plik

<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="text-middle big-spacer-right flex-0" className="text-middle big-spacer-right flex-0"
name="John Doe" name="John Doe"
size={36} size={36}

+ 1
- 1
server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx Wyświetl plik

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

+ 1
- 1
server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformation.tsx Wyświetl plik

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


interface Props { interface Props {

+ 2
- 1
server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx Wyświetl plik

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

+ 2
- 1
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx Wyświetl plik

import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages'; import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages';
import { get, save } from '../../../helpers/storage'; import { get, save } from '../../../helpers/storage';
import { isLoggedIn } from '../../../helpers/users'; import { isLoggedIn } from '../../../helpers/users';
import { AppState } from '../../../types/appstate';
import { ComponentQualifier } from '../../../types/component'; 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 { hasFilterParams, hasViewParams, parseUrlQuery, Query } from '../query';
import '../styles.css'; import '../styles.css';
import { Facets, Project } from '../types'; import { Facets, Project } from '../types';

+ 2
- 1
server/sonar-web/src/main/js/apps/projects/components/ApplicationCreation.tsx Wyświetl plik

import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { getComponentAdminUrl, getComponentOverviewUrl } from '../../../helpers/urls'; import { getComponentAdminUrl, getComponentOverviewUrl } from '../../../helpers/urls';
import { hasGlobalPermission } from '../../../helpers/users'; import { hasGlobalPermission } from '../../../helpers/users';
import { AppState } from '../../../types/appstate';
import { ComponentQualifier } from '../../../types/component'; import { ComponentQualifier } from '../../../types/component';
import { Permissions } from '../../../types/permissions'; import { Permissions } from '../../../types/permissions';
import { AppState, LoggedInUser } from '../../../types/types';
import { LoggedInUser } from '../../../types/types';


export interface ApplicationCreationProps { export interface ApplicationCreationProps {
appState: AppState; appState: AppState;

+ 2
- 1
server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx Wyświetl plik

import SelectLegacy from '../../components/controls/SelectLegacy'; import SelectLegacy from '../../components/controls/SelectLegacy';
import QualifierIcon from '../../components/icons/QualifierIcon'; import QualifierIcon from '../../components/icons/QualifierIcon';
import { translate } from '../../helpers/l10n'; 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 BulkApplyTemplateModal from './BulkApplyTemplateModal';
import DeleteModal from './DeleteModal'; import DeleteModal from './DeleteModal';



+ 2
- 7
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx Wyświetl plik

import { Alert } from '../../../components/ui/Alert'; import { Alert } from '../../../components/ui/Alert';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { isDiffMetric } from '../../../helpers/measures'; import { isDiffMetric } from '../../../helpers/measures';
import { AppState } from '../../../types/appstate';
import { MetricKey } from '../../../types/metrics'; 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 Condition from './Condition';
import ConditionModal from './ConditionModal'; import ConditionModal from './ConditionModal';



+ 1
- 1
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/PermissionItem-test.tsx.snap Wyświetl plik

<div <div
className="display-flex-center permission-list-item padded" className="display-flex-center permission-list-item padded"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right" className="spacer-right"
name="John Doe" name="John Doe"
size={32} size={32}

+ 1
- 1
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/QualityGatePermissionsAddModalRenderer-test.tsx.snap Wyświetl plik



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

+ 1
- 1
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfilePermissionsUser-test.tsx.snap Wyświetl plik

className="pull-right spacer-top spacer-left spacer-right button-small" className="pull-right spacer-top spacer-left spacer-right button-small"
onClick={[Function]} onClick={[Function]}
/> />
<Connect(Avatar)
<withAppStateContext(Avatar)
className="pull-left spacer-right" className="pull-left spacer-right"
name="Luke Skywalker" name="Luke Skywalker"
size={32} size={32}

+ 13
- 13
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotReviewHistory-test.tsx.snap Wyświetl plik

<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="Luke Skywalker" name="Luke Skywalker"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="Luke Skywalker" name="Luke Skywalker"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="Luke Skywalker" name="Luke Skywalker"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="Luke Skywalker" name="Luke Skywalker"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="john.doe" name="john.doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}
<div <div
className="display-flex-center" className="display-flex-center"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="John Doe" name="John Doe"
size={20} size={20}

+ 2
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/assignee/__tests__/__snapshots__/AssigneeSelectionRenderer-test.tsx.snap Wyświetl plik

key="john.doe" key="john.doe"
onClick={[Function]} onClick={[Function]}
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right" className="spacer-right"
name="John Doe" name="John Doe"
size={16} size={16}
key="highlighted" key="highlighted"
onClick={[Function]} onClick={[Function]}
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right" className="spacer-right"
name="John Doe" name="John Doe"
size={16} size={16}

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx Wyświetl plik

import { IndexLink } from 'react-router'; import { IndexLink } from 'react-router';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../../helpers/urls'; 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 { getCategoryName } from '../utils';
import { ADDITIONAL_CATEGORIES } from './AdditionalCategories'; import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
import CATEGORY_OVERRIDES from './CategoryOverrides'; import CATEGORY_OVERRIDES from './CategoryOverrides';

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx Wyświetl plik

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


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

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/components/almIntegration/CreationTooltip.tsx Wyświetl plik

import { getEdition, getEditionUrl } from '../../../../helpers/editions'; import { getEdition, getEditionUrl } from '../../../../helpers/editions';
import { translate } from '../../../../helpers/l10n'; import { translate } from '../../../../helpers/l10n';
import { AlmKeys } from '../../../../types/alm-settings'; import { AlmKeys } from '../../../../types/alm-settings';
import { AppState } from '../../../../types/appstate';
import { EditionKey } from '../../../../types/editions'; import { EditionKey } from '../../../../types/editions';
import { AppState } from '../../../../types/types';


export interface CreationTooltipProps { export interface CreationTooltipProps {
alm: AlmKeys; alm: AlmKeys;

+ 2
- 1
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx Wyświetl plik

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


export interface AlmSpecificFormProps { export interface AlmSpecificFormProps {
alm: AlmKeys; alm: AlmKeys;

+ 0
- 55
server/sonar-web/src/main/js/apps/settings/store/__tests__/actions-test.ts Wyświetl plik

/*
* 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 Wyświetl plik

/*
* 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 Wyświetl plik

/*
* 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 Wyświetl plik

import { Alert } from '../../../components/ui/Alert'; import { Alert } from '../../../components/ui/Alert';
import { toShortNotSoISOString } from '../../../helpers/dates'; import { toShortNotSoISOString } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import PageActions from './PageActions'; import PageActions from './PageActions';


export interface Props { export interface Props {

+ 2
- 2
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UserListItem-test.tsx.snap Wyświetl plik

<td <td
className="thin nowrap text-middle" className="thin nowrap text-middle"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
name="One" name="One"
size={36} size={36}
/> />
<td <td
className="thin nowrap text-middle" className="thin nowrap text-middle"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
name="One" name="One"
size={36} size={36}
/> />

+ 4
- 4
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.tsx.snap Wyświetl plik

role="listitem" role="listitem"
title="Administrator" title="Administrator"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
name="Administrator" name="Administrator"
size={16} size={16}
/> />
role="listitem" role="listitem"
title="Administrator" title="Administrator"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
hash="7daf6c79d4802916d83f6266e24850af" hash="7daf6c79d4802916d83f6266e24850af"
name="Administrator" name="Administrator"
size={16} size={16}
<div <div
className="Select-value-label" className="Select-value-label"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
hash="7daf6c79d4802916d83f6266e24850af" hash="7daf6c79d4802916d83f6266e24850af"
name="Administrator" name="Administrator"
size={16} size={16}
<div <div
className="Select-value-label" className="Select-value-label"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
name="Administrator" name="Administrator"
size={16} size={16}
/> />

+ 2
- 1
server/sonar-web/src/main/js/components/controls/ComponentReportActions.tsx Wyświetl plik

import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage'; import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage';
import { translate, translateWithParameters } from '../../helpers/l10n'; import { translate, translateWithParameters } from '../../helpers/l10n';
import { isLoggedIn } from '../../helpers/users'; import { isLoggedIn } from '../../helpers/users';
import { AppState } from '../../types/appstate';
import { Branch } from '../../types/branch-like'; import { Branch } from '../../types/branch-like';
import { ComponentQualifier } from '../../types/component'; import { ComponentQualifier } from '../../types/component';
import { ComponentReportStatus } from '../../types/component-report'; 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 { withCurrentUser } from '../hoc/withCurrentUser';
import ComponentReportActionsRenderer from './ComponentReportActionsRenderer'; import ComponentReportActionsRenderer from './ComponentReportActionsRenderer';



+ 1
- 1
server/sonar-web/src/main/js/components/docs/DocLink.tsx Wyświetl plik

import withAppStateContext from '../../app/components/app-state/withAppStateContext'; import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import DetachIcon from '../../components/icons/DetachIcon'; import DetachIcon from '../../components/icons/DetachIcon';
import { isSonarCloud } from '../../helpers/system'; import { isSonarCloud } from '../../helpers/system';
import { AppState } from '../../types/types';
import { AppState } from '../../types/appstate';


interface OwnProps { interface OwnProps {
appState: AppState; appState: AppState;

+ 3
- 3
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap Wyświetl plik

<span <span
className="text-top" className="text-top"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="" name=""
<span <span
className="text-top" className="text-top"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="" name=""
<span <span
className="text-top" className="text-top"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="" name=""

+ 4
- 4
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.tsx.snap Wyświetl plik

className="issue-comment-author" className="issue-comment-author"
title="John Doe" title="John Doe"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="John Doe" name="John Doe"
className="issue-comment-author" className="issue-comment-author"
title="John Doe" title="John Doe"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="John Doe" name="John Doe"
className="issue-comment-author" className="issue-comment-author"
title="John Doe" title="John Doe"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="John Doe" name="John Doe"
className="issue-comment-author" className="issue-comment-author"
title="user.x_deleted.john.doe" title="user.x_deleted.john.doe"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="john.doe" name="john.doe"

+ 2
- 2
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/ChangelogPopup-test.tsx.snap Wyświetl plik

className="text-left text-top" className="text-left text-top"
> >
<p> <p>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
hash="gravatarhash" hash="gravatarhash"
name="John Doe" name="John Doe"
className="text-left text-top" className="text-left text-top"
> >
<p> <p>
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-right" className="little-spacer-right"
name="john.doe" name="john.doe"
size={16} size={16}

+ 1
- 1
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetAssigneePopup-test.tsx.snap Wyświetl plik

item="luke" item="luke"
key="luke" key="luke"
> >
<Connect(Avatar)
<withAppStateContext(Avatar)
className="spacer-right" className="spacer-right"
name="Skywalker" name="Skywalker"
size={16} size={16}

+ 2
- 2
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SimilarIssuesPopup-test.tsx.snap Wyświetl plik

> >
<span> <span>
assigned_to assigned_to
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-left little-spacer-right" className="little-spacer-left little-spacer-right"
name="Luke Skywalker" name="Luke Skywalker"
size={16} size={16}
> >
<span> <span>
assigned_to assigned_to
<Connect(Avatar)
<withAppStateContext(Avatar)
className="little-spacer-left little-spacer-right" className="little-spacer-left little-spacer-right"
name="luke" name="luke"
size={16} size={16}

+ 3
- 2
server/sonar-web/src/main/js/components/measure/Measure.tsx Wyświetl plik

import Level from '../../components/ui/Level'; import Level from '../../components/ui/Level';
import Rating from '../../components/ui/Rating'; import Rating from '../../components/ui/Rating';
import { formatMeasure } from '../../helpers/measures'; import { formatMeasure } from '../../helpers/measures';
import { getRatingTooltip } from './utils';
import RatingTooltipContent from './RatingTooltipContent';


interface Props { interface Props {
className?: string; className?: string;
return <span className={className}>{formattedValue != null ? formattedValue : '–'}</span>; 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} />; const rating = <Rating small={small} value={value} />;

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

+ 94
- 0
server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx Wyświetl plik

/*
* 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 Wyświetl plik

import * as React from 'react'; import * as React from 'react';
import Measure from '../Measure'; 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', () => { it('renders trivial measure', () => {
expect( expect(
shallow(<Measure metricKey="coverage" metricType="PERCENT" value="73.0" />) shallow(<Measure metricKey="coverage" metricType="PERCENT" value="73.0" />)
).toMatchSnapshot(); ).toMatchSnapshot();
}); });


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


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

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

+ 62
- 0
server/sonar-web/src/main/js/components/measure/__tests__/RatingTooltipContent-test.tsx Wyświetl plik

/*
* 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 Wyświetl plik

/> />
`; `;


exports[`renders known RATING 1`] = `
exports[`renders RATING 1`] = `
<Tooltip <Tooltip
overlay="tooltip"
overlay={
<withAppStateContext(RatingTooltipContent)
metricKey="sqale_rating"
value="3"
/>
}
> >
<span> <span>
<Rating <Rating
</span> </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 Wyświetl plik

// 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 Wyświetl plik

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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'; import { Dict, Measure, MeasureEnhanced, MeasureIntern, Metric } from '../../types/types';


const KNOWN_RATINGS = [
export const KNOWN_RATINGS = [
'sqale_rating', 'sqale_rating',
'maintainability_rating', // Needed to provide the label for "new_maintainability_rating" 'maintainability_rating', // Needed to provide the label for "new_maintainability_rating"
'reliability_rating', 'reliability_rating',
export function getLeakValue(measure: MeasureIntern | undefined): string | undefined { export function getLeakValue(measure: MeasureIntern | undefined): string | undefined {
return measure?.period?.value; 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 Wyświetl plik

import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
import { translate } from '../../../../helpers/l10n'; import { translate } from '../../../../helpers/l10n';
import { AlmKeys } from '../../../../types/alm-settings'; import { AlmKeys } from '../../../../types/alm-settings';
import { AppState } from '../../../../types/types';
import { AppState } from '../../../../types/appstate';
import SentenceWithHighlights from '../../components/SentenceWithHighlights'; import SentenceWithHighlights from '../../components/SentenceWithHighlights';


export interface PublishStepsProps { export interface PublishStepsProps {

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/AnalysisCommand.tsx Wyświetl plik

import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; 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 { CompilationInfo } from '../components/CompilationInfo';
import CreateYmlFile from '../components/CreateYmlFile'; import CreateYmlFile from '../components/CreateYmlFile';
import { BuildTools } from '../types'; import { BuildTools } from '../types';

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx Wyświetl plik

import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system'; import { getBaseUrl } from '../../../helpers/system';
import { AlmKeys } from '../../../types/alm-settings'; import { AlmKeys } from '../../../types/alm-settings';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import SentenceWithHighlights from './SentenceWithHighlights'; import SentenceWithHighlights from './SentenceWithHighlights';


export interface AllSetProps { export interface AllSetProps {

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx Wyświetl plik

*/ */
import * as React from 'react'; import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; 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 { BuildTools } from '../types';
import CFamily from './commands/CFamily'; import CFamily from './commands/CFamily';
import DotNet from './commands/DotNet'; import DotNet from './commands/DotNet';

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx Wyświetl plik

import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
import { ClipboardIconButton } from '../../../components/controls/clipboard'; import { ClipboardIconButton } from '../../../components/controls/clipboard';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/types';
import { AppState } from '../../../types/appstate';
import FinishButton from '../components/FinishButton'; import FinishButton from '../components/FinishButton';
import GithubCFamilyExampleRepositories from '../components/GithubCFamilyExampleRepositories'; import GithubCFamilyExampleRepositories from '../components/GithubCFamilyExampleRepositories';
import Step from '../components/Step'; import Step from '../components/Step';

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/jenkins/JenkinsTutorial.tsx Wyświetl plik

AlmSettingsInstance, AlmSettingsInstance,
ProjectAlmBindingResponse ProjectAlmBindingResponse
} from '../../../types/alm-settings'; } 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 AllSetStep from '../components/AllSetStep';
import JenkinsfileStep from './JenkinsfileStep'; import JenkinsfileStep from './JenkinsfileStep';
import MultiBranchPipelineStep from './MultiBranchPipelineStep'; import MultiBranchPipelineStep from './MultiBranchPipelineStep';

+ 29
- 27
server/sonar-web/src/main/js/components/ui/Avatar.tsx Wyświetl plik

*/ */
import classNames from 'classnames'; import classNames from 'classnames';
import * as React from 'react'; 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 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 { interface Props {
appState: AppState;
className?: string; className?: string;
enableGravatar: boolean;
gravatarServerUrl: string;
hash?: string; hash?: string;
name?: string; name?: string;
size: number; 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 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 ( return (
<img <img
alt={props.name}
className={classNames(props.className, 'rounded')}
height={props.size}
alt={name}
className={classNames(className, 'rounded')}
height={size}
src={url} 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 Wyświetl plik

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; 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}'; const gravatarServerUrl = 'http://example.com/{EMAIL_MD5}.jpg?s={SIZE}';


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


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


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

+ 1
- 1
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx Wyświetl plik

import * as React from 'react'; import * as React from 'react';
import withAppStateContext from '../../app/components/app-state/withAppStateContext'; import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { translate } from '../../helpers/l10n'; import { translate } from '../../helpers/l10n';
import { AppState } from '../../types/appstate';
import { EditionKey } from '../../types/editions'; import { EditionKey } from '../../types/editions';
import { SystemUpgrade } from '../../types/system'; import { SystemUpgrade } from '../../types/system';
import { AppState } from '../../types/types';
import { ResetButtonLink } from '../controls/buttons'; import { ResetButtonLink } from '../controls/buttons';
import Modal from '../controls/Modal'; import Modal from '../controls/Modal';
import { Alert, AlertVariant } from '../ui/Alert'; import { Alert, AlertVariant } from '../ui/Alert';

+ 0
- 26
server/sonar-web/src/main/js/helpers/analytics.js Wyświetl plik

/*
* 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 Wyświetl plik

return metricKey.indexOf('new_') === 0; 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[]) { export function getDisplayMetrics(metrics: Metric[]) {
return metrics.filter(metric => !metric.hidden && !['DATA', 'DISTRIB'].includes(metric.type)); return metrics.filter(metric => !metric.hidden && !['DATA', 'DISTRIB'].includes(metric.type));
} }

+ 0
- 0
server/sonar-web/src/main/js/helpers/testMocks.ts Wyświetl plik


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików

Ładowanie…
Anuluj
Zapisz