*/
import throwGlobalError from '../app/utils/throwGlobalError';
import { getJSON } from '../helpers/request';
-import { Language } from '../types/types';
+import { Language } from '../types/languages';
export function getLanguages(): Promise<Language[]> {
return getJSON('/api/languages/list').then(r => r.languages, throwGlobalError);
import * as React from 'react';
import { connect } from 'react-redux';
import { lazyLoadComponent } from '../../components/lazyLoadComponent';
-import { fetchLanguages } from '../../store/rootActions';
import { getGlobalSettingValue, Store } from '../../store/rootReducer';
import KeyboardShortcutsModal from './KeyboardShortcutsModal';
const PageTracker = lazyLoadComponent(() => import('./PageTracker'));
interface Props {
- fetchLanguages: () => void;
enableGravatar: boolean;
gravatarServerUrl: string;
}
componentDidMount() {
this.mounted = true;
- this.props.fetchLanguages();
this.setScrollbarWidth();
}
};
};
-const mapDispatchToProps = { fetchLanguages };
-
-export default connect(mapStateToProps, mapDispatchToProps)(App);
+export default connect(mapStateToProps)(App);
import GlobalMessagesContainer from './GlobalMessagesContainer';
import IndexationContextProvider from './indexation/IndexationContextProvider';
import IndexationNotification from './indexation/IndexationNotification';
+import LanguageContextProvider from './languages/LanguagesContextProvider';
import GlobalNav from './nav/global/GlobalNav';
import PromotionNotification from './promotion-notification/PromotionNotification';
import StartupModal from './StartupModal';
<div className="page-container">
<Workspace>
<IndexationContextProvider>
- <GlobalNav location={props.location} />
- <GlobalMessagesContainer />
- <IndexationNotification />
- <UpdateNotification dismissable={true} />
- {props.children}
+ <LanguageContextProvider>
+ <GlobalNav location={props.location} />
+ <GlobalMessagesContainer />
+ <IndexationNotification />
+ <UpdateNotification dismissable={true} />
+ {props.children}
+ </LanguageContextProvider>
</IndexationContextProvider>
</Workspace>
</div>
import { shallow } from 'enzyme';
import * as React from 'react';
import { connect } from 'react-redux';
-import { fetchLanguages as realFetchLanguages } from '../../../store/rootActions';
import { App } from '../App';
jest.mock('react-redux', () => ({
).toMatchSnapshot('with gravatar');
});
-it('should correctly fetch available languages', () => {
- const fetchLanguages = jest.fn();
- shallowRender({ fetchLanguages });
- expect(fetchLanguages).toBeCalled();
-});
-
it('should correctly set the scrollbar width as a custom property', () => {
shallowRender();
expect(document.body.style.getPropertyValue('--sbw')).toBe('0px');
});
describe('redux', () => {
- it('should correctly map state and dispatch props', () => {
- const [mapStateToProps, mapDispatchToProps] = (connect as jest.Mock).mock.calls[0];
+ it('should correctly map state props', () => {
+ const [mapStateToProps] = (connect as jest.Mock).mock.calls[0];
expect(mapStateToProps({})).toEqual({
enableGravatar: true,
gravatarServerUrl: 'http://gravatar.com'
});
- expect(mapDispatchToProps).toEqual(
- expect.objectContaining({ fetchLanguages: realFetchLanguages })
- );
});
});
function shallowRender(props: Partial<App['props']> = {}) {
- return shallow<App>(
- <App fetchLanguages={jest.fn()} enableGravatar={false} gravatarServerUrl="" {...props} />
- );
+ return shallow<App>(<App enableGravatar={false} gravatarServerUrl="" {...props} />);
}
>
<Workspace>
<Connect(withAppState(IndexationContextProvider))>
- <Connect(GlobalNav)
- location={
- Object {
- "action": "PUSH",
- "hash": "",
- "key": "key",
- "pathname": "/path",
- "query": Object {},
- "search": "",
- "state": Object {},
+ <LanguageContextProvider>
+ <Connect(GlobalNav)
+ location={
+ Object {
+ "action": "PUSH",
+ "hash": "",
+ "key": "key",
+ "pathname": "/path",
+ "query": Object {},
+ "search": "",
+ "state": Object {},
+ }
}
- }
- />
- <Connect(GlobalMessages) />
- <Connect(withCurrentUser(withIndexationContext(IndexationNotification))) />
- <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
- dismissable={true}
- />
- <ChildComponent />
+ />
+ <Connect(GlobalMessages) />
+ <Connect(withCurrentUser(withIndexationContext(IndexationNotification))) />
+ <Connect(withCurrentUser(Connect(withAppState(UpdateNotification))))
+ dismissable={true}
+ />
+ <ChildComponent />
+ </LanguageContextProvider>
</Connect(withAppState(IndexationContextProvider))>
</Workspace>
</div>
--- /dev/null
+/*
+ * 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 { Languages } from '../../../types/languages';
+
+export const LanguagesContext = React.createContext<Languages>({});
--- /dev/null
+/*
+ * 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 } from 'lodash';
+import * as React from 'react';
+import { getLanguages } from '../../../api/languages';
+import { Languages } from '../../../types/languages';
+import { LanguagesContext } from './LanguagesContext';
+
+interface State {
+ languages: Languages;
+}
+
+export default class LanguageContextProvider extends React.PureComponent<{}, State> {
+ mounted = false;
+ state: State = {
+ languages: {}
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+
+ this.loadData();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadData = async () => {
+ const languageList = await getLanguages().catch(() => []);
+ this.setState({ languages: keyBy(languageList, 'key') });
+ };
+
+ render() {
+ return (
+ <LanguagesContext.Provider value={this.state.languages}>
+ {this.props.children}
+ </LanguagesContext.Provider>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { getLanguages } from '../../../../api/languages';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import LanguageContextProvider from '../LanguagesContextProvider';
+
+jest.mock('../../../../api/languages', () => ({
+ getLanguages: jest.fn().mockResolvedValue({})
+}));
+
+it('should call language', async () => {
+ const languages = { c: { key: 'c', name: 'c' } };
+ (getLanguages as jest.Mock).mockResolvedValueOnce(languages);
+ const wrapper = shallowRender();
+
+ expect(getLanguages).toBeCalled();
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state()).toEqual({ languages });
+});
+
+function shallowRender() {
+ return shallow<LanguageContextProvider>(
+ <LanguageContextProvider>
+ <div />
+ </LanguageContextProvider>
+ );
+}
--- /dev/null
+/*
+ * 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 { Languages } from '../../../../types/languages';
+import withLanguagesContext from '../withLanguagesContext';
+
+jest.mock('../LanguagesContext', () => {
+ return {
+ LanguagesContext: {
+ Consumer: ({ children }: { children: (props: {}) => React.ReactNode }) => {
+ return children({ c: { key: 'c', name: 'c' } });
+ }
+ }
+ };
+});
+
+class Wrapped extends React.Component<{ languages: Languages }> {
+ render() {
+ return <div />;
+ }
+}
+
+const UnderTest = withLanguagesContext(Wrapped);
+
+it('should inject languages', () => {
+ const wrapper = shallow(<UnderTest />);
+ expect(wrapper.dive().type()).toBe(Wrapped);
+ expect(wrapper.dive<Wrapped>().props().languages).toEqual({ c: { key: 'c', name: 'c' } });
+});
--- /dev/null
+/*
+ * 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 { getWrappedDisplayName } from '../../../components/hoc/utils';
+import { Languages } from '../../../types/languages';
+import { LanguagesContext } from './LanguagesContext';
+
+export interface WithLanguagesContextProps {
+ languages: Languages;
+}
+
+export default function withLanguagesContext<P>(
+ WrappedComponent: React.ComponentType<P & WithLanguagesContextProps>
+) {
+ return class WithLanguagesContext extends React.PureComponent<
+ Omit<P, keyof WithLanguagesContextProps>
+ > {
+ static displayName = getWrappedDisplayName(WrappedComponent, 'withLanguagesContext');
+
+ render() {
+ return (
+ <LanguagesContext.Consumer>
+ {languages => <WrappedComponent languages={languages} {...(this.props as P)} />}
+ </LanguagesContext.Consumer>
+ );
+ }
+ };
+}
}
}
/>
- <Connect(MetaQualityProfiles)
+ <withLanguagesContext(MetaQualityProfiles)
headerClassName="big-spacer-top"
profiles={
Array [
}
}
/>
- <Connect(MetaQualityProfiles)
+ <withLanguagesContext(MetaQualityProfiles)
headerClassName="big-spacer-top"
profiles={
Array [
}
}
/>
- <Connect(MetaQualityProfiles)
+ <withLanguagesContext(MetaQualityProfiles)
headerClassName="big-spacer-top"
profiles={
Array [
}
}
/>
- <Connect(MetaQualityProfiles)
+ <withLanguagesContext(MetaQualityProfiles)
headerClassName="big-spacer-top"
profiles={
Array [
}
}
/>
- <Connect(MetaQualityProfiles)
+ <withLanguagesContext(MetaQualityProfiles)
headerClassName="big-spacer-top"
profiles={
Array [
}
}
/>
- <Connect(MetaQualityProfiles)
+ <withLanguagesContext(MetaQualityProfiles)
headerClassName="big-spacer-top"
profiles={
Array [
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
import { Link } from 'react-router';
import { searchRules } from '../../../../../../api/rules';
import Tooltip from '../../../../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../../../../helpers/urls';
-import { getLanguages, Store } from '../../../../../../store/rootReducer';
-import { ComponentQualityProfile, Dict, Languages } from '../../../../../../types/types';
+import { Languages } from '../../../../../../types/languages';
+import { ComponentQualityProfile, Dict } from '../../../../../../types/types';
+import withLanguagesContext from '../../../../languages/withLanguagesContext';
-interface StateProps {
- languages: Languages;
-}
-
-interface OwnProps {
+interface Props {
headerClassName?: string;
+ languages: Languages;
profiles: ComponentQualityProfile[];
}
deprecatedByKey: Dict<number>;
}
-export class MetaQualityProfiles extends React.PureComponent<StateProps & OwnProps, State> {
+export class MetaQualityProfiles extends React.PureComponent<Props, State> {
mounted = false;
state: State = { deprecatedByKey: {} };
}
}
-const mapStateToProps = (state: Store) => ({
- languages: getLanguages(state)
-});
-
-export default connect(mapStateToProps)(MetaQualityProfiles);
+export default withLanguagesContext(MetaQualityProfiles);
} from '../../../helpers/pages';
import { scrollToElement } from '../../../helpers/scrolling';
import { isLoggedIn } from '../../../helpers/users';
-import { getCurrentUser, getLanguages, Store } from '../../../store/rootReducer';
+import { getCurrentUser, Store } from '../../../store/rootReducer';
import { SecurityStandard } from '../../../types/security';
-import {
- CurrentUser,
- Dict,
- Languages,
- Paging,
- RawQuery,
- Rule,
- RuleActivation
-} from '../../../types/types';
+import { CurrentUser, Dict, Paging, RawQuery, Rule, RuleActivation } from '../../../types/types';
import {
shouldOpenSonarSourceSecurityFacet,
shouldOpenStandardsChildFacet,
interface Props extends WithRouterProps {
currentUser: CurrentUser;
- languages: Languages;
}
interface State {
isFiltered = () => Object.keys(serializeQuery(this.state.query)).length > 0;
renderBulkButton = () => {
- const { currentUser, languages } = this.props;
+ const { currentUser } = this.props;
const { canWrite, paging, query, referencedProfiles } = this.state;
const canUpdate = canWrite || Object.values(referencedProfiles).some(p => p.actions?.edit);
return (
paging && (
- <BulkChange
- languages={languages}
- query={query}
- referencedProfiles={referencedProfiles}
- total={paging.total}
- />
+ <BulkChange query={query} referencedProfiles={referencedProfiles} total={paging.total} />
)
);
};
}
const mapStateToProps = (state: Store) => ({
- currentUser: getCurrentUser(state),
- languages: getLanguages(state)
+ currentUser: getCurrentUser(state)
});
export default withRouter(connect(mapStateToProps)(App));
import Tooltip from '../../../components/controls/Tooltip';
import { PopupPlacement } from '../../../components/ui/popups';
import { translate } from '../../../helpers/l10n';
-import { Dict, Languages } from '../../../types/types';
+import { Dict } from '../../../types/types';
import { Query } from '../query';
import BulkChangeModal from './BulkChangeModal';
interface Props {
- languages: Languages;
query: Query;
referencedProfiles: Dict<Profile>;
total: number;
{this.state.modal && this.state.action && (
<BulkChangeModal
action={this.state.action}
- languages={this.props.languages}
onClose={this.closeModal}
profile={this.state.profile}
query={this.props.query}
*/
import * as React from 'react';
import { bulkActivateRules, bulkDeactivateRules, Profile } from '../../../api/quality-profiles';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
import Modal from '../../../components/controls/Modal';
import SelectLegacy from '../../../components/controls/SelectLegacy';
import { Alert } from '../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
-import { Dict, Languages } from '../../../types/types';
+import { Languages } from '../../../types/languages';
+import { Dict } from '../../../types/types';
import { Query, serializeQuery } from '../query';
interface Props {
submitting: boolean;
}
-export default class BulkChangeModal extends React.PureComponent<Props, State> {
+export class BulkChangeModal extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
);
}
}
+
+export default withLanguagesContext(BulkChangeModal);
*/
import { uniqBy } from 'lodash';
import * as React from 'react';
-import { connect } from 'react-redux';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import ListStyleFacet from '../../../components/facet/ListStyleFacet';
import { translate } from '../../../helpers/l10n';
import { highlightTerm } from '../../../helpers/search';
-import { getLanguages, Store } from '../../../store/rootReducer';
+import { Language, Languages } from '../../../types/languages';
import { BasicProps } from './Facet';
-interface InstalledLanguage {
- key: string;
- name: string;
-}
-
interface Props extends BasicProps {
disabled?: boolean;
- installedLanguages: InstalledLanguage[];
+ languages: Languages;
}
-class LanguageFacet extends React.PureComponent<Props> {
+export class LanguageFacet extends React.PureComponent<Props> {
getLanguageName = (languageKey: string) => {
- const language = this.props.installedLanguages.find(l => l.key === languageKey);
+ const language = this.props.languages[languageKey];
return language ? language.name : languageKey;
};
};
getAllPossibleOptions = () => {
- const { installedLanguages, stats = {} } = this.props;
+ const { languages, stats = {} } = this.props;
// add any language that presents in the facet, but might not be installed
// for such language we don't know their display name, so let's just use their key
// and make sure we reference each language only once
- return uniqBy(
- [...installedLanguages, ...Object.keys(stats).map(key => ({ key, name: key }))],
- language => language.key
+ return uniqBy<Language>(
+ [...Object.values(languages), ...Object.keys(stats).map(key => ({ key, name: key }))],
+ (language: Language) => language.key
);
};
- renderSearchResult = ({ name }: InstalledLanguage, term: string) => {
+ renderSearchResult = ({ name }: Language, term: string) => {
return highlightTerm(name, term);
};
render() {
return (
- <ListStyleFacet<InstalledLanguage>
+ <ListStyleFacet<Language>
disabled={this.props.disabled}
disabledHelper={translate('coding_rules.filters.language.inactive')}
facetHeader={translate('coding_rules.facet.languages')}
}
}
-const mapStateToProps = (state: Store) => ({
- installedLanguages: Object.values(getLanguages(state))
-});
-
-export default connect(mapStateToProps)(LanguageFacet);
+export default withLanguagesContext(LanguageFacet);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
import { getRuleRepositories } from '../../../api/rules';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import ListStyleFacet from '../../../components/facet/ListStyleFacet';
import { translate } from '../../../helpers/l10n';
import { highlightTerm } from '../../../helpers/search';
-import { getLanguages, Store } from '../../../store/rootReducer';
+import { Languages } from '../../../types/languages';
import { Dict } from '../../../types/types';
import { BasicProps } from './Facet';
interface StateProps {
- referencedLanguages: Dict<{ key: string; name: string }>;
+ languages: Languages;
}
interface Props extends BasicProps, StateProps {
export class RepositoryFacet extends React.PureComponent<Props> {
getLanguageName = (languageKey: string) => {
- const { referencedLanguages } = this.props;
- const language = referencedLanguages[languageKey];
+ const { languages } = this.props;
+ const language = languages[languageKey];
return (language && language.name) || languageKey;
};
}
}
-const mapStateToProps = (state: Store): StateProps => ({
- referencedLanguages: getLanguages(state)
-});
-
-export default connect(mapStateToProps)(RepositoryFacet);
+export default withLanguagesContext(RepositoryFacet);
currentUser={mockCurrentUser({
isLoggedIn: true
})}
- languages={{ js: { key: 'js', name: 'JavaScript' } }}
location={mockLocation()}
params={{}}
router={mockRouter()}
it('should display BulkChangeModal', () => {
const wrapper = shallowRender();
wrapper.instance().handleActivateClick(mockEvent());
- expect(wrapper.find('BulkChangeModal')).toMatchSnapshot();
+ expect(wrapper.find('withLanguagesContext(BulkChangeModal)').exists()).toBe(true);
});
function shallowRender(props: Partial<BulkChange['props']> = {}) {
return shallow<BulkChange>(
<BulkChange
- languages={{ js: { key: 'js', name: 'JavaScript' } }}
query={{ activation: false, profile: 'key' } as BulkChange['props']['query']}
referencedProfiles={{ key: profile }}
total={2}
import { mockLanguage, mockQualityProfile } from '../../../../helpers/testMocks';
import { submit, waitAndUpdate } from '../../../../helpers/testUtils';
import { Query } from '../../query';
-import BulkChangeModal from '../BulkChangeModal';
+import { BulkChangeModal } from '../BulkChangeModal';
jest.mock('../../../../api/quality-profiles', () => ({
bulkActivateRules: jest.fn().mockResolvedValue({ failed: 0, succeeded: 2 }),
it('should correctly enable/disable the language facet', () => {
const wrapper = shallowRender({ query: { profile: 'foo' } as Query });
- expect(wrapper.find('Connect(LanguageFacet)').prop('disabled')).toBe(true);
+ expect(wrapper.find('withLanguagesContext(LanguageFacet)').prop('disabled')).toBe(true);
wrapper.setProps({ query: {} }).update();
- expect(wrapper.find('Connect(LanguageFacet)').prop('disabled')).toBe(false);
+ expect(wrapper.find('withLanguagesContext(LanguageFacet)').prop('disabled')).toBe(false);
});
it('should correctly enable/disable the activation severity facet', () => {
--- /dev/null
+/*
+ * 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 { mockLanguage } from '../../../../helpers/testMocks';
+import { LanguageFacet } from '../LanguageFacet';
+
+it('should handle search correctly', async () => {
+ const wrapper = shallowRender({ stats: { java: 12 } });
+ const result = await wrapper.instance().handleSearch('ja');
+
+ expect(result).toStrictEqual({
+ paging: {
+ pageIndex: 1,
+ pageSize: 2,
+ total: 2
+ },
+ results: [
+ { key: 'js', name: 'javascript' },
+ { key: 'java', name: 'java' }
+ ]
+ });
+});
+
+it('should render name correctly', () => {
+ const wrapper = shallowRender();
+
+ expect(wrapper.instance().getLanguageName('js')).toBe('javascript');
+ expect(wrapper.instance().getLanguageName('unknownKey')).toBe('unknownKey');
+});
+
+it('should render search results correctly', () => {
+ const wrapper = shallowRender();
+
+ expect(wrapper.instance().renderSearchResult({ key: 'hello', name: 'Hello' }, 'llo'))
+ .toMatchInlineSnapshot(`
+ <React.Fragment>
+ He
+ <mark>
+ llo
+ </mark>
+ </React.Fragment>
+ `);
+});
+
+function shallowRender(props: Partial<LanguageFacet['props']> = {}) {
+ return shallow<LanguageFacet>(
+ <LanguageFacet
+ languages={{
+ js: mockLanguage({ key: 'js', name: 'javascript' }),
+ c: mockLanguage({ key: 'c', name: 'c' })
+ }}
+ onChange={jest.fn()}
+ onToggle={jest.fn()}
+ open={false}
+ stats={{}}
+ values={[]}
+ {...props}
+ />
+ );
+}
function shallowRender(props: Partial<RepositoryFacet['props']> = {}) {
return shallow<RepositoryFacet>(
<RepositoryFacet
- referencedLanguages={{ l: mockLanguage() }}
+ languages={{ l: mockLanguage() }}
referencedRepositories={{
l: mockRuleRepository(),
noName: mockRuleRepository({ name: undefined })
exports[`renderBulkButton should show bulk change button when user has edit rights on specific quality profile 1`] = `
<BulkChange
- languages={
- Object {
- "js": Object {
- "key": "js",
- "name": "JavaScript",
- },
- }
- }
query={
Object {
"activation": undefined,
exports[`renderBulkButton should show bulk change button when user has global admin rights on quality profiles 1`] = `
<BulkChange
- languages={
- Object {
- "js": Object {
- "key": "js",
- "name": "JavaScript",
- },
- }
- }
query={
Object {
"activation": undefined,
className="display-flex-space-between"
>
<BulkChange
- languages={
- Object {
- "js": Object {
- "key": "js",
- "name": "JavaScript",
- },
- }
- }
query={
Object {
"activation": undefined,
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should display BulkChangeModal 1`] = `
-<BulkChangeModal
- action="activate"
- languages={
- Object {
- "js": Object {
- "key": "js",
- "name": "JavaScript",
- },
- }
- }
- onClose={[Function]}
- query={
- Object {
- "activation": false,
- "profile": "key",
- }
- }
- referencedProfiles={
- Object {
- "key": Object {
- "actions": Object {
- "associateProjects": true,
- "copy": true,
- "delete": false,
- "edit": true,
- "setAsDefault": true,
- },
- "activeDeprecatedRuleCount": 2,
- "activeRuleCount": 10,
- "childrenCount": 0,
- "depth": 1,
- "isBuiltIn": false,
- "isDefault": false,
- "isInherited": false,
- "key": "key",
- "language": "js",
- "languageName": "JavaScript",
- "name": "name",
- "projectCount": 3,
- },
- }
- }
- total={2}
-/>
-`;
-
exports[`should not a disabled button when edition is not possible 1`] = `
<Tooltip
overlay="coding_rules.can_not_bulk_change"
exports[`should render correctly 1`] = `
<Fragment>
- <Connect(LanguageFacet)
+ <withLanguagesContext(LanguageFacet)
disabled={false}
onChange={[MockFunction]}
onToggle={[MockFunction]}
onToggle={[MockFunction]}
open={false}
/>
- <Connect(RepositoryFacet)
+ <withLanguagesContext(RepositoryFacet)
onChange={[MockFunction]}
onToggle={[MockFunction]}
open={false}
it('should display secondary measure too', () => {
const wrapper = shallow(<MeasureHeader {...PROPS} secondaryMeasure={SECONDARY} />);
- expect(wrapper.find('Connect(LanguageDistribution)')).toHaveLength(1);
+ expect(wrapper.find('withLanguagesContext(LanguageDistribution)')).toHaveLength(1);
});
it('should work with measure without value', () => {
*/
import { omit, uniqBy } from 'lodash';
import * as React from 'react';
-import { connect } from 'react-redux';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import ListStyleFacet from '../../../components/facet/ListStyleFacet';
import { translate } from '../../../helpers/l10n';
import { highlightTerm } from '../../../helpers/search';
-import { getLanguages, Store } from '../../../store/rootReducer';
import { Facet, ReferencedLanguage } from '../../../types/issues';
+import { Language, Languages } from '../../../types/languages';
import { Dict } from '../../../types/types';
import { Query } from '../utils';
-interface InstalledLanguage {
- key: string;
- name: string;
-}
-
interface Props {
fetching: boolean;
- installedLanguages: InstalledLanguage[];
- languages: string[];
+ languages: Languages;
+ selectedLanguages: string[];
loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
};
getAllPossibleOptions = () => {
- const { installedLanguages, stats = {} } = this.props;
+ const { languages, stats = {} } = this.props;
// add any language that presents in the facet, but might not be installed
// for such language we don't know their display name, so let's just use their key
// and make sure we reference each language only once
return uniqBy(
- [...installedLanguages, ...Object.keys(stats).map(key => ({ key, name: key }))],
+ [...Object.values(languages), ...Object.keys(stats).map(key => ({ key, name: key }))],
language => language.key
);
};
- loadSearchResultCount = (languages: InstalledLanguage[]) => {
+ loadSearchResultCount = (languages: Language[]) => {
return this.props.loadSearchResultCount('languages', {
languages: languages.map(language => language.key)
});
};
- renderSearchResult = ({ name }: InstalledLanguage, term: string) => {
+ renderSearchResult = ({ name }: Language, term: string) => {
return highlightTerm(name, term);
};
render() {
return (
- <ListStyleFacet<InstalledLanguage>
+ <ListStyleFacet<Language>
facetHeader={translate('issues.facet.languages')}
fetching={this.props.fetching}
getFacetItemText={this.getLanguageName}
renderSearchResult={this.renderSearchResult}
searchPlaceholder={translate('search.search_for_languages')}
stats={this.props.stats}
- values={this.props.languages}
+ values={this.props.selectedLanguages}
/>
);
}
}
-const mapStateToProps = (state: Store) => ({
- installedLanguages: Object.values(getLanguages(state))
-});
-
-export default connect(mapStateToProps)(LanguageFacet);
+export default withLanguagesContext(LanguageFacet);
/>
<LanguageFacet
fetching={this.props.loadingFacets.languages === true}
- languages={query.languages}
loadSearchResultCount={this.props.loadSearchResultCount}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
open={!!openFacets.languages}
query={query}
referencedLanguages={this.props.referencedLanguages}
+ selectedLanguages={query.languages}
stats={facets.languages}
/>
<RuleFacet
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"DirectoryFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"FileFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"DirectoryFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
- "Connect(LanguageFacet)",
+ "withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
project_quality_profile.add_language.action
</Button>
</div>
- <Connect(AddLanguageModal)
+ <withLanguagesContext(AddLanguageModal)
onClose={[MockFunction]}
onSubmit={[MockFunction]}
profilesByLanguage={
*/
import { difference } from 'lodash';
import * as React from 'react';
-import { connect } from 'react-redux';
import { Link } from 'react-router';
import { Profile } from '../../../api/quality-profiles';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import DisableableSelectOption from '../../../components/common/DisableableSelectOption';
import { ButtonLink, SubmitButton } from '../../../components/controls/buttons';
import SelectLegacy from '../../../components/controls/SelectLegacy';
import SimpleModal from '../../../components/controls/SimpleModal';
import { translate } from '../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../helpers/urls';
-import { Store } from '../../../store/rootReducer';
-import { Dict, Languages } from '../../../types/types';
+import { Languages } from '../../../types/languages';
+import { Dict } from '../../../types/types';
export interface AddLanguageModalProps {
languages: Languages;
);
}
-function mapStateToProps({ languages }: Store) {
- return { languages };
-}
-
-export default connect(mapStateToProps)(AddLanguageModal);
+export default withLanguagesContext(AddLanguageModal);
import { RawQuery } from '../../../types/types';
import CoverageFilter from '../filters/CoverageFilter';
import DuplicationsFilter from '../filters/DuplicationsFilter';
-import LanguagesFilterContainer from '../filters/LanguagesFilterContainer';
+import LanguagesFilter from '../filters/LanguagesFilter';
import MaintainabilityFilter from '../filters/MaintainabilityFilter';
import NewCoverageFilter from '../filters/NewCoverageFilter';
import NewDuplicationsFilter from '../filters/NewDuplicationsFilter';
/>
</>
)}
- <LanguagesFilterContainer
+ <LanguagesFilter
{...facetProps}
facet={getFacet(facets, 'languages')}
query={query}
<NewLinesFilter
onQueryChange={[MockFunction]}
/>
- <Connect(LanguagesFilter)
+ <withLanguagesContext(LanguagesFilter)
onQueryChange={[MockFunction]}
query={
Object {
<NewLinesFilter
onQueryChange={[MockFunction]}
/>
- <Connect(LanguagesFilter)
+ <withLanguagesContext(LanguagesFilter)
onQueryChange={[MockFunction]}
query={
Object {
onQueryChange={[MockFunction]}
value="3"
/>
- <Connect(LanguagesFilter)
+ <withLanguagesContext(LanguagesFilter)
onQueryChange={[MockFunction]}
query={
Object {
onQueryChange={[MockFunction]}
value="3"
/>
- <Connect(LanguagesFilter)
+ <withLanguagesContext(LanguagesFilter)
onQueryChange={[MockFunction]}
query={
Object {
import { CurrentUser } from '../../../../types/types';
import { Project } from '../../types';
import './ProjectCard.css';
-import ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer';
+import ProjectCardLanguages from './ProjectCardLanguages';
import ProjectCardMeasure from './ProjectCardMeasure';
import ProjectCardMeasures from './ProjectCardMeasures';
import ProjectCardQualityGate from './ProjectCardQualityGate';
<span className="spacer-left">
<SizeRating value={Number(measures[MetricKey.ncloc])} />
</span>
- <ProjectCardLanguagesContainer
+ <ProjectCardLanguages
className="small spacer-left text-ellipsis"
distribution={measures[MetricKey.ncloc_language_distribution]}
/>
*/
import { sortBy } from 'lodash';
import * as React from 'react';
+import withLanguagesContext from '../../../../app/components/languages/withLanguagesContext';
import { translate } from '../../../../helpers/l10n';
-import { Languages } from '../../../../types/types';
+import { Languages } from '../../../../types/languages';
interface Props {
className?: string;
languages: Languages;
}
-export default function ProjectCardLanguages({ className, distribution, languages }: Props) {
+export function ProjectCardLanguages({ className, distribution, languages }: Props) {
if (distribution === undefined) {
return null;
}
const language = languages[key];
return language != null ? language.name : key;
}
+
+export default withLanguagesContext(ProjectCardLanguages);
+++ /dev/null
-/*
- * 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 { getLanguages, Store } from '../../../../store/rootReducer';
-import ProjectCardLanguages from './ProjectCardLanguages';
-
-const stateToProps = (state: Store) => ({
- languages: getLanguages(state)
-});
-
-export default connect(stateToProps)(ProjectCardLanguages);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import ProjectCardLanguages from '../ProjectCardLanguages';
+import { ProjectCardLanguages } from '../ProjectCardLanguages';
const languages = {
java: { key: 'java', name: 'Java' },
*/
import { difference, sortBy } from 'lodash';
import * as React from 'react';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import { translate } from '../../../helpers/l10n';
-import { getLanguageByKey } from '../../../store/languages';
-import { Dict, Languages, RawQuery } from '../../../types/types';
+import { Languages } from '../../../types/languages';
+import { Dict, RawQuery } from '../../../types/types';
import { Facet } from '../types';
import Filter from './Filter';
import FilterHeader from './FilterHeader';
value?: string[];
}
-export default class LanguagesFilter extends React.Component<Props> {
+export class LanguagesFilter extends React.Component<Props> {
getSearchOptions = () => {
const { facet, languages } = this.props;
let languageKeys = Object.keys(languages);
sortBy(Object.keys(facet), [(option: string) => -facet[option], (option: string) => option]);
renderOption = (option: string) => (
- <SearchableFilterOption
- option={getLanguageByKey(this.props.languages, option)}
- optionKey={option}
- />
+ <SearchableFilterOption option={this.props.languages[option]} optionKey={option} />
);
render() {
);
}
}
+
+export default withLanguagesContext(LanguagesFilter);
+++ /dev/null
-/*
- * 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 { getLanguages, Store } from '../../../store/rootReducer';
-import LanguagesFilter from './LanguagesFilter';
-
-const stateToProps = (state: Store) => ({
- languages: getLanguages(state)
-});
-
-export default connect(stateToProps)(LanguagesFilter);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import LanguagesFilter from '../LanguagesFilter';
+import { LanguagesFilter } from '../LanguagesFilter';
const languages = {
java: { key: 'java', name: 'Java' },
+++ /dev/null
-/*
- * 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 { Helmet } from 'react-helmet-async';
-import { Actions, getExporters, searchQualityProfiles } from '../../../api/quality-profiles';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { translate } from '../../../helpers/l10n';
-import { Languages } from '../../../types/types';
-import '../styles.css';
-import { Exporter, Profile } from '../types';
-import { sortProfiles } from '../utils';
-
-interface Props {
- children: React.ReactElement<any>;
- languages: Languages;
-}
-
-interface State {
- actions?: Actions;
- loading: boolean;
- exporters?: Exporter[];
- profiles?: Profile[];
-}
-
-export default class App extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = { loading: true };
-
- componentDidMount() {
- this.mounted = true;
- this.loadData();
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchProfiles() {
- return searchQualityProfiles();
- }
-
- loadData() {
- this.setState({ loading: true });
- Promise.all([getExporters(), this.fetchProfiles()]).then(
- responses => {
- if (this.mounted) {
- const [exporters, profilesResponse] = responses;
- this.setState({
- actions: profilesResponse.actions,
- exporters,
- profiles: sortProfiles(profilesResponse.profiles),
- loading: false
- });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- }
-
- updateProfiles = () => {
- return this.fetchProfiles().then(r => {
- if (this.mounted) {
- this.setState({ profiles: sortProfiles(r.profiles) });
- }
- });
- };
-
- renderChild() {
- if (this.state.loading) {
- return <i className="spinner" />;
- }
- const finalLanguages = Object.values(this.props.languages);
-
- return React.cloneElement(this.props.children, {
- actions: this.state.actions || {},
- profiles: this.state.profiles || [],
- languages: finalLanguages,
- exporters: this.state.exporters,
- updateProfiles: this.updateProfiles
- });
- }
-
- render() {
- return (
- <div className="page page-limited">
- <Suggestions suggestions="quality_profiles" />
- <Helmet defer={false} title={translate('quality_profiles.page')} />
-
- {this.renderChild()}
- </div>
- );
- }
-}
+++ /dev/null
-/*
- * 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 { getLanguages, Store } from '../../../store/rootReducer';
-import App from './App';
-
-const mapStateToProps = (state: Store) => ({
- languages: getLanguages(state)
-});
-
-export default connect(mapStateToProps)(App);
--- /dev/null
+/*
+ * 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 { Helmet } from 'react-helmet-async';
+import { Actions, getExporters, searchQualityProfiles } from '../../../api/quality-profiles';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
+import { translate } from '../../../helpers/l10n';
+import { Languages } from '../../../types/languages';
+import '../styles.css';
+import { Exporter, Profile } from '../types';
+import { sortProfiles } from '../utils';
+
+interface Props {
+ children: React.ReactElement;
+ languages: Languages;
+}
+
+interface State {
+ actions?: Actions;
+ loading: boolean;
+ exporters?: Exporter[];
+ profiles?: Profile[];
+}
+
+export class QualityProfilesApp extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadData();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchProfiles() {
+ return searchQualityProfiles();
+ }
+
+ loadData() {
+ this.setState({ loading: true });
+ Promise.all([getExporters(), this.fetchProfiles()]).then(
+ ([exporters, profilesResponse]) => {
+ if (this.mounted) {
+ this.setState({
+ actions: profilesResponse.actions,
+ exporters,
+ profiles: sortProfiles(profilesResponse.profiles),
+ loading: false
+ });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ }
+
+ updateProfiles = () => {
+ return this.fetchProfiles().then(r => {
+ if (this.mounted) {
+ this.setState({ profiles: sortProfiles(r.profiles) });
+ }
+ });
+ };
+
+ renderChild() {
+ if (this.state.loading) {
+ return <i className="spinner" />;
+ }
+ const finalLanguages = Object.values(this.props.languages);
+
+ return React.cloneElement(this.props.children, {
+ actions: this.state.actions || {},
+ profiles: this.state.profiles || [],
+ languages: finalLanguages,
+ exporters: this.state.exporters,
+ updateProfiles: this.updateProfiles
+ });
+ }
+
+ render() {
+ return (
+ <div className="page page-limited">
+ <Suggestions suggestions="quality_profiles" />
+ <Helmet defer={false} title={translate('quality_profiles.page')} />
+
+ {this.renderChild()}
+ </div>
+ );
+ }
+}
+
+export default withLanguagesContext(QualityProfilesApp);
+++ /dev/null
-/*
- * 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 App from '../App';
-
-it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<App['props']> = {}) {
- return shallow<App>(
- <App languages={{}} {...props}>
- <div />
- </App>
- );
-}
+++ /dev/null
-/*
- * 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 '../AppContainer';
-
-jest.mock('react-redux', () => ({
- connect: jest.fn(() => (a: any) => a)
-}));
-
-jest.mock('../../../../store/rootReducer', () => {
- return {
- getLanguages: jest.fn(() => [
- { key: 'css', name: 'CSS' },
- { key: 'js', name: 'JS' }
- ])
- };
-});
-
-describe('redux', () => {
- it('should correctly map state and dispatch props', () => {
- const [mapStateToProps] = (connect as jest.Mock).mock.calls[0];
- const { languages } = mapStateToProps({});
-
- expect(languages).toEqual([
- { key: 'css', name: 'CSS' },
- { key: 'js', name: 'JS' }
- ]);
- });
-});
--- /dev/null
+/*
+ * 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 { Actions, getExporters, searchQualityProfiles } from '../../../../api/quality-profiles';
+import {
+ mockLanguage,
+ mockQualityProfile,
+ mockQualityProfileExporter
+} from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { QualityProfilesApp } from '../QualityProfilesApp';
+
+jest.mock('../../../../api/quality-profiles', () => ({
+ getExporters: jest.fn().mockResolvedValue([]),
+ searchQualityProfiles: jest.fn().mockResolvedValue({ profiles: [] })
+}));
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot('loading');
+
+ expect(getExporters).toBeCalled();
+ expect(searchQualityProfiles).toBeCalled();
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot('full');
+});
+
+it('should render child with additional props', () => {
+ const language = mockLanguage();
+ const wrapper = shallowRender({ languages: { [language.key]: language } });
+
+ const actions: Actions = { create: true };
+ const profiles = [mockQualityProfile()];
+ const exporters = [mockQualityProfileExporter()];
+
+ wrapper.setState({ loading: false, actions, profiles, exporters });
+
+ expect(wrapper.childAt(2).props()).toEqual({
+ actions,
+ profiles,
+ languages: [language],
+ exporters,
+ updateProfiles: wrapper.instance().updateProfiles
+ });
+});
+
+it('should handle update', async () => {
+ const profile1 = mockQualityProfile({ key: 'qp1', name: 'An amazing profile' });
+ const profile2 = mockQualityProfile({ key: 'qp2', name: 'Quality Profile' });
+
+ // Mock one call for the initial load, one for the update
+ (searchQualityProfiles as jest.Mock)
+ .mockResolvedValueOnce({ profiles: [] })
+ .mockResolvedValueOnce({ profiles: [profile2, profile1] });
+
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().profiles).toHaveLength(0);
+
+ wrapper.instance().updateProfiles();
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().profiles).toEqual([profile1, profile2]);
+});
+
+function shallowRender(props: Partial<QualityProfilesApp['props']> = {}) {
+ return shallow<QualityProfilesApp>(
+ <QualityProfilesApp languages={{}} {...props}>
+ <div />
+ </QualityProfilesApp>
+ );
+}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="page page-limited"
->
- <Suggestions
- suggestions="quality_profiles"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- title="quality_profiles.page"
- />
- <i
- className="spinner"
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: full 1`] = `
+<div
+ className="page page-limited"
+>
+ <Suggestions
+ suggestions="quality_profiles"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="quality_profiles.page"
+ />
+ <div
+ actions={Object {}}
+ exporters={Array []}
+ languages={Array []}
+ profiles={Array []}
+ updateProfiles={[Function]}
+ />
+</div>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<div
+ className="page page-limited"
+>
+ <Suggestions
+ suggestions="quality_profiles"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="quality_profiles.page"
+ />
+ <i
+ className="spinner"
+ />
+</div>
+`;
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { Alert } from '../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Dict, Language } from '../../../types/types';
+import { Language } from '../../../types/languages';
+import { Dict } from '../../../types/types';
import { Profile } from '../types';
import ProfilesListHeader from './ProfilesListHeader';
import ProfilesListRow from './ProfilesListRow';
const routes = [
{
- component: lazyLoadComponent(() => import('./components/AppContainer')),
+ component: lazyLoadComponent(() => import('./components/QualityProfilesApp')),
indexRoute: { component: lazyLoadComponent(() => import('./home/HomeContainer')) },
childRoutes: [
{
*/
import { sortBy } from 'lodash';
import * as React from 'react';
-import { connect } from 'react-redux';
+import withLanguagesContext from '../../app/components/languages/withLanguagesContext';
import Histogram from '../../components/charts/Histogram';
import { translate } from '../../helpers/l10n';
import { formatMeasure } from '../../helpers/measures';
-import { getLanguages, Store } from '../../store/rootReducer';
+import { Languages } from '../../types/languages';
import { MetricType } from '../../types/metrics';
-import { Languages } from '../../types/types';
interface LanguageDistributionProps {
distribution: string;
return name.length > 10 ? `${name.substr(0, 7)}...` : name;
}
-const mapStateToProps = (state: Store) => ({
- languages: getLanguages(state)
-});
-
-export default connect(mapStateToProps)(LanguageDistribution);
+export default withLanguagesContext(LanguageDistribution);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockStore } from '../../../helpers/testMocks';
import { withCLanguageFeature } from '../withCLanguageFeature';
+jest.mock('../../../app/components/languages/LanguagesContext', () => {
+ return {
+ LanguagesContext: {
+ Consumer: ({ children }: { children: (props: {}) => React.ReactNode }) => {
+ return children({ c: { key: 'c', name: 'c' } });
+ }
+ }
+ };
+});
+
class X extends React.Component<{ hasCLanguageFeature: boolean }> {
render() {
return <div />;
const UnderTest = withCLanguageFeature(X);
it('should pass if C Language feature is available', () => {
- const wrapper = shallow(<UnderTest />, {
- context: { store: mockStore({ languages: { c: {} } }) }
- });
+ const wrapper = shallow(<UnderTest />);
expect(wrapper.dive().type()).toBe(X);
expect(wrapper.dive<X>().props().hasCLanguageFeature).toBe(true);
});
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
-import { getLanguages, Store } from '../../store/rootReducer';
+import { LanguagesContext } from '../../app/components/languages/LanguagesContext';
import { getWrappedDisplayName } from './utils';
export function withCLanguageFeature<P>(
WrappedComponent: React.ComponentType<P & { hasCLanguageFeature: boolean }>
) {
- class Wrapper extends React.Component<P & { hasCLanguageFeature: boolean }> {
+ class Wrapper extends React.Component<Omit<P, 'hasCLanguageFeature'>> {
static displayName = getWrappedDisplayName(WrappedComponent, 'withCLanguageFeature');
render() {
- return <WrappedComponent {...this.props} />;
- }
- }
+ return (
+ <LanguagesContext.Consumer>
+ {languages => {
+ const hasCLanguageFeature = languages['c'] !== undefined;
- function mapStateToProps(state: Store) {
- const languages = getLanguages(state);
- const hasCLanguageFeature = languages['c'] !== undefined;
-
- return { hasCLanguageFeature };
+ return (
+ <WrappedComponent {...(this.props as P)} hasCLanguageFeature={hasCLanguageFeature} />
+ );
+ }}
+ </LanguagesContext.Consumer>
+ );
+ }
}
- return connect(mapStateToProps)(Wrapper);
+ return Wrapper;
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { connect } from 'react-redux';
+import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import { translate } from '../../../helpers/l10n';
-import { getLanguages, Store } from '../../../store/rootReducer';
-import { Component, Languages } from '../../../types/types';
+import { Languages } from '../../../types/languages';
+import { Component } from '../../../types/types';
import RenderOptions from '../components/RenderOptions';
import { BuildTools } from '../types';
import AnalysisCommand from './commands/AnalysisCommand';
export interface BranchesAnalysisStepProps {
languages: Languages;
component: Component;
+
onStepValidationChange: (isValid: boolean) => void;
}
);
}
-const mapStateToProps = (state: Store) => ({
- languages: getLanguages(state)
-});
-
-export default connect(mapStateToProps)(BranchAnalysisStepContent);
+export default withLanguagesContext(BranchAnalysisStepContent);
className="boxed-group-inner"
>
<div>
- <Connect(BranchAnalysisStepContent)
+ <withLanguagesContext(BranchAnalysisStepContent)
component={
Object {
"breadcrumbs": Array [],
`;
exports[`should render correctly: yaml file step content 1`] = `
-<Connect(withCLanguageFeature(YamlFileStep))>
+<withCLanguageFeature(YamlFileStep)>
[Function]
-</Connect(withCLanguageFeature(YamlFileStep))>
+</withCLanguageFeature(YamlFileStep)>
`;
`;
exports[`should render correctly: yaml file step content 1`] = `
-<Connect(withCLanguageFeature(YamlFileStep))>
+<withCLanguageFeature(YamlFileStep)>
[Function]
-</Connect(withCLanguageFeature(YamlFileStep))>
+</withCLanguageFeature(YamlFileStep)>
`;
onboarding.tutorial.with.gitlab_ci.title
</h1>
</div>
- <Connect(withCLanguageFeature(ProjectKeyStep))
+ <withCLanguageFeature(ProjectKeyStep)
component={
Object {
"breadcrumbs": Array [],
}
}
/>
- <Connect(withCLanguageFeature(JenkinsfileStep))
+ <withCLanguageFeature(JenkinsfileStep)
baseUrl=""
component={
Object {
}
}
/>
- <Connect(withCLanguageFeature(JenkinsfileStep))
+ <withCLanguageFeature(JenkinsfileStep)
baseUrl=""
component={
Object {
import { createStore, Store } from 'redux';
import { DocumentationEntry } from '../apps/documentation/utils';
import { Exporter, Profile } from '../apps/quality-profiles/types';
+import { Language } from '../types/languages';
import { DumpStatus, DumpTask } from '../types/project-dump';
import { TaskStatuses } from '../types/tasks';
import {
HealthType,
IdentityProvider,
Issue,
- Language,
LoggedInUser,
Measure,
MeasureEnhanced,
+++ /dev/null
-/*
- * 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 } from 'lodash';
-import { Languages } from '../types/types';
-import { ActionType } from './utils/actions';
-
-export function receiveLanguages(languages: Array<{ key: string; name: string }>) {
- return { type: 'RECEIVE_LANGUAGES', languages };
-}
-
-type Action = ActionType<typeof receiveLanguages, 'RECEIVE_LANGUAGES'>;
-
-export default function(state: Languages = {}, action: Action): Languages {
- if (action.type === 'RECEIVE_LANGUAGES') {
- return keyBy(action.languages, 'key');
- }
-
- return state;
-}
-
-export function getLanguages(state: Languages) {
- return state;
-}
-
-export function getLanguageByKey(state: Languages, key: string) {
- return state[key];
-}
import { InjectedRouter } from 'react-router';
import { Dispatch } from 'redux';
import * as auth from '../api/auth';
-import { getLanguages } from '../api/languages';
import { getAllMetrics } from '../api/metrics';
import { getQualityGateProjectStatus } from '../api/quality-gates';
import { getBranchLikeQuery } from '../helpers/branch-like';
import { requireAuthorization as requireAuthorizationAction } from './appState';
import { registerBranchStatusAction } from './branches';
import { addGlobalErrorMessage } from './globalMessages';
-import { receiveLanguages } from './languages';
import { receiveMetrics } from './metrics';
-export function fetchLanguages() {
- return (dispatch: Dispatch) => {
- getLanguages().then(
- languages => dispatch(receiveLanguages(languages)),
- () => {
- /* do nothing */
- }
- );
- };
-}
-
export function fetchMetrics() {
return (dispatch: Dispatch) => {
getAllMetrics().then(
import { combineReducers } from 'redux';
import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer';
import { BranchLike } from '../types/branch-like';
-import { AppState, CurrentUserSettingNames, Languages } from '../types/types';
+import { AppState, CurrentUserSettingNames } from '../types/types';
import appState from './appState';
import branches, * as fromBranches from './branches';
import globalMessages, * as fromGlobalMessages from './globalMessages';
-import languages, * as fromLanguages from './languages';
import metrics, * as fromMetrics from './metrics';
import users, * as fromUsers from './users';
appState: AppState;
branches: fromBranches.State;
globalMessages: fromGlobalMessages.State;
- languages: Languages;
+
metrics: fromMetrics.State;
users: fromUsers.State;
appState,
branches,
globalMessages,
- languages,
metrics,
users,
return fromGlobalMessages.getGlobalMessages(state.globalMessages);
}
-export function getLanguages(state: Store) {
- return fromLanguages.getLanguages(state.languages);
-}
-
export function getCurrentUserSetting(state: Store, key: CurrentUserSettingNames) {
return fromUsers.getCurrentUserSetting(state.users, key);
}
--- /dev/null
+/*
+ * 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 { Dict } from './types';
+
+export interface Language {
+ key: string;
+ name: string;
+}
+
+export type Languages = Dict<Language>;