import { getJSON, post, RequestData } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import { IndexationStatus } from '../types/indexation';
-import { Task } from '../types/tasks';
+import { Task, TaskWarning } from '../types/tasks';
export function getAnalysisStatus(data: {
component: string;
name: string;
organization?: string;
pullRequest?: string;
- warnings: string[];
+ warnings: TaskWarning[];
};
}> {
return getJSON('/api/ce/analysis_status', data).catch(throwGlobalError);
export function getIndexationStatus(): Promise<IndexationStatus> {
return getJSON('/api/ce/indexation_status').catch(throwGlobalError);
}
+
+export function dismissAnalysisWarning(component: string, warning: string) {
+ return post('/api/ce/dismiss_analysis_warning', { component, warning }).catch(throwGlobalError);
+}
} from '../../store/rootActions';
import { BranchLike } from '../../types/branch-like';
import { isPortfolioLike } from '../../types/component';
-import { Task, TaskStatuses } from '../../types/tasks';
+import { Task, TaskStatuses, TaskWarning } from '../../types/tasks';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import { ComponentContext } from './ComponentContext';
import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToIndexation';
isPending: boolean;
loading: boolean;
tasksInProgress?: Task[];
- warnings: string[];
+ warnings: TaskWarning[];
}
const FETCH_STATUS_WAIT_TIME = 3000;
}
};
+ handleWarningDismiss = () => {
+ const { component } = this.state;
+ if (component !== undefined) {
+ this.fetchWarnings(component);
+ }
+ };
+
render() {
const { component, loading } = this.state;
isInProgress={isInProgress}
isPending={isPending}
onComponentChange={this.handleComponentChange}
+ onWarningDismiss={this.handleWarningDismiss}
warnings={this.state.warnings}
/>
)}
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getBranches, getPullRequests } from '../../../api/branches';
-import { getTasksForComponent } from '../../../api/ce';
+import { getAnalysisStatus, getTasksForComponent } from '../../../api/ce';
import { getComponentData } from '../../../api/components';
import { getComponentNavigation } from '../../../api/nav';
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like';
expect(wrapper.find(PageUnavailableDueToIndexation).exists()).toBe(true);
});
+it('should correctly reload last task warnings if anything got dismissed', async () => {
+ (getComponentData as jest.Mock<any>).mockResolvedValueOnce({
+ component: mockComponent({
+ breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: ComponentQualifier.Project }]
+ })
+ });
+ (getComponentNavigation as jest.Mock).mockResolvedValueOnce({});
+
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ (getAnalysisStatus as jest.Mock).mockClear();
+
+ wrapper.instance().handleWarningDismiss();
+ expect(getAnalysisStatus).toBeCalledTimes(1);
+});
+
function shallowRender(props: Partial<ComponentContainer['props']> = {}) {
return shallow<ComponentContainer>(
<ComponentContainer
import ContextNavBar from 'sonar-ui-common/components/ui/ContextNavBar';
import { BranchLike } from '../../../../types/branch-like';
import { ComponentQualifier } from '../../../../types/component';
-import { Task, TaskStatuses } from '../../../../types/tasks';
+import { Task, TaskStatuses, TaskWarning } from '../../../../types/tasks';
import { rawSizes } from '../../../theme';
import RecentHistory from '../../RecentHistory';
import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif';
import InfoDrawer from './projectInformation/InfoDrawer';
import ProjectInformation from './projectInformation/ProjectInformation';
-interface Props {
+export interface ComponentNavProps {
branchLikes: BranchLike[];
currentBranchLike: BranchLike | undefined;
component: T.Component;
isInProgress?: boolean;
isPending?: boolean;
onComponentChange: (changes: Partial<T.Component>) => void;
- warnings: string[];
+ onWarningDismiss: () => void;
+ warnings: TaskWarning[];
}
-export default function ComponentNav(props: Props) {
+export default function ComponentNav(props: ComponentNavProps) {
const {
branchLikes,
component,
component={component}
currentBranchLike={currentBranchLike}
/>
- <HeaderMeta branchLike={currentBranchLike} component={component} warnings={warnings} />
+ <HeaderMeta
+ branchLike={currentBranchLike}
+ component={component}
+ onWarningDismiss={props.onWarningDismiss}
+ warnings={warnings}
+ />
</div>
<Menu
branchLike={currentBranchLike}
import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate } from 'sonar-ui-common/helpers/l10n';
+import { TaskWarning } from '../../../../types/tasks';
const AnalysisWarningsModal = lazyLoadComponent(
() => import('../../../../components/common/AnalysisWarningsModal'),
);
interface Props {
- warnings: string[];
+ componentKey: string;
+ onWarningDismiss: () => void;
+ warnings: TaskWarning[];
}
interface State {
/>
</Alert>
{this.state.modal && (
- <AnalysisWarningsModal onClose={this.handleCloseModal} warnings={this.props.warnings} />
+ <AnalysisWarningsModal
+ componentKey={this.props.componentKey}
+ onClose={this.handleCloseModal}
+ onWarningDismiss={this.props.onWarningDismiss}
+ warnings={this.props.warnings}
+ />
)}
</>
);
import { getCurrentUser, Store } from '../../../../store/rootReducer';
import { BranchLike } from '../../../../types/branch-like';
import { ComponentQualifier } from '../../../../types/component';
+import { TaskWarning } from '../../../../types/tasks';
import ComponentNavWarnings from './ComponentNavWarnings';
import './HeaderMeta.css';
branchLike?: BranchLike;
currentUser: T.CurrentUser;
component: T.Component;
- warnings: string[];
+ onWarningDismiss: () => void;
+ warnings: TaskWarning[];
}
export function HeaderMeta(props: HeaderMetaProps) {
<div className="display-flex-center flex-0 small">
{warnings.length > 0 && (
<span className="header-meta-warnings">
- <ComponentNavWarnings warnings={warnings} />
+ <ComponentNavWarnings
+ componentKey={component.key}
+ onWarningDismiss={props.onWarningDismiss}
+ warnings={warnings}
+ />
</span>
)}
{component.analysisDate && (
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import ComponentNav from '../ComponentNav';
-
-const component = {
- breadcrumbs: [{ key: 'component', name: 'component', qualifier: 'TRK' }],
- key: 'component',
- name: 'component',
- organization: 'org',
- qualifier: 'TRK'
-};
-
-it('renders', () => {
- const wrapper = shallow(
+import { mockTask, mockTaskWarning } from '../../../../../helpers/mocks/tasks';
+import { mockComponent } from '../../../../../helpers/testMocks';
+import { ComponentQualifier } from '../../../../../types/component';
+import { TaskStatuses } from '../../../../../types/tasks';
+import RecentHistory from '../../../RecentHistory';
+import ComponentNav, { ComponentNavProps } from '../ComponentNav';
+import Menu from '../Menu';
+import InfoDrawer from '../projectInformation/InfoDrawer';
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ jest.spyOn(React, 'useEffect').mockImplementationOnce(f => f());
+});
+
+it('renders correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ warnings: [mockTaskWarning()] })).toMatchSnapshot('has warnings');
+ expect(shallowRender({ isInProgress: true })).toMatchSnapshot('has in progress notification');
+ expect(shallowRender({ isPending: true })).toMatchSnapshot('has pending notification');
+ expect(shallowRender({ currentTask: mockTask({ status: TaskStatuses.Failed }) })).toMatchSnapshot(
+ 'has failed notification'
+ );
+});
+
+it('correctly adds data to the history if there are breadcrumbs', () => {
+ const key = 'foo';
+ const name = 'Foo';
+ const organization = 'baz';
+ const qualifier = ComponentQualifier.Portfolio;
+ const spy = jest.spyOn(RecentHistory, 'add');
+
+ shallowRender({
+ component: mockComponent({
+ key,
+ name,
+ organization,
+ breadcrumbs: [
+ {
+ key: 'bar',
+ name: 'Bar',
+ qualifier
+ }
+ ]
+ })
+ });
+
+ expect(spy).toBeCalledWith(key, name, qualifier.toLowerCase(), organization);
+});
+
+it('correctly toggles the project info display', () => {
+ const wrapper = shallowRender();
+ expect(wrapper.find(InfoDrawer).props().displayed).toBe(false);
+
+ wrapper
+ .find(Menu)
+ .props()
+ .onToggleProjectInfo();
+ expect(wrapper.find(InfoDrawer).props().displayed).toBe(true);
+
+ wrapper
+ .find(Menu)
+ .props()
+ .onToggleProjectInfo();
+ expect(wrapper.find(InfoDrawer).props().displayed).toBe(false);
+
+ wrapper
+ .find(Menu)
+ .props()
+ .onToggleProjectInfo();
+ wrapper
+ .find(InfoDrawer)
+ .props()
+ .onClose();
+ expect(wrapper.find(InfoDrawer).props().displayed).toBe(false);
+});
+
+function shallowRender(props: Partial<ComponentNavProps> = {}) {
+ return shallow<ComponentNavProps>(
<ComponentNav
branchLikes={[]}
- component={component}
+ component={mockComponent({
+ breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: ComponentQualifier.Project }]
+ })}
currentBranchLike={undefined}
- isInProgress={true}
- isPending={true}
+ isInProgress={false}
+ isPending={false}
onComponentChange={jest.fn()}
+ onWarningDismiss={jest.fn()}
warnings={[]}
+ {...props}
/>
);
- expect(wrapper).toMatchSnapshot();
-});
+}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockTaskWarning } from '../../../../../helpers/mocks/tasks';
import ComponentNavWarnings from '../ComponentNavWarnings';
it('should render', () => {
- const wrapper = shallow(<ComponentNavWarnings warnings={['warning 1']} />);
+ const wrapper = shallow(
+ <ComponentNavWarnings
+ componentKey="foo"
+ onWarningDismiss={jest.fn()}
+ warnings={[mockTaskWarning({ message: 'warning 1' })]}
+ />
+ );
wrapper.setState({ modal: true });
expect(wrapper).toMatchSnapshot();
});
import * as React from 'react';
import HomePageSelect from '../../../../../components/controls/HomePageSelect';
import { mockBranch, mockPullRequest } from '../../../../../helpers/mocks/branch-like';
+import { mockTaskWarning } from '../../../../../helpers/mocks/tasks';
import { mockComponent, mockCurrentUser } from '../../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../../types/component';
import { getCurrentPage, HeaderMeta, HeaderMetaProps } from '../HeaderMeta';
branchLike={mockBranch()}
component={mockComponent({ analysisDate: '2017-01-02T00:00:00.000Z', version: '0.0.1' })}
currentUser={mockCurrentUser({ isLoggedIn: true })}
- warnings={['ERROR_1', 'ERROR_2']}
+ onWarningDismiss={jest.fn()}
+ warnings={[mockTaskWarning({ message: 'ERROR_1' }), mockTaskWarning({ message: 'ERROR_2' })]}
{...props}
/>
);
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`renders 1`] = `
+exports[`renders correctly: default 1`] = `
+<ContextNavBar
+ height={72}
+ id="context-navigation"
+>
+ <div
+ className="display-flex-center display-flex-space-between little-padded-top padded-bottom"
+ >
+ <Connect(Component)
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ <Connect(HeaderMeta)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onWarningDismiss={[MockFunction]}
+ warnings={Array []}
+ />
+ </div>
+ <Connect(withAppState(Menu))
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isInProgress={false}
+ isPending={false}
+ onToggleProjectInfo={[Function]}
+ />
+ <InfoDrawer
+ displayed={false}
+ onClose={[Function]}
+ top={120}
+ >
+ <Connect(ProjectInformation)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onComponentChange={[MockFunction]}
+ />
+ </InfoDrawer>
+</ContextNavBar>
+`;
+
+exports[`renders correctly: has failed notification 1`] = `
+<ContextNavBar
+ height={102}
+ id="context-navigation"
+ notif={
+ <ComponentNavBgTaskNotif
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ currentTask={
+ Object {
+ "analysisId": "x123",
+ "componentKey": "foo",
+ "componentName": "Foo",
+ "componentQualifier": "TRK",
+ "id": "AXR8jg_0mF2ZsYr8Wzs2",
+ "organization": "bar",
+ "status": "FAILED",
+ "submittedAt": "2020-09-11T11:45:35+0200",
+ "type": "REPORT",
+ }
+ }
+ isInProgress={false}
+ isPending={false}
+ />
+ }
+>
+ <div
+ className="display-flex-center display-flex-space-between little-padded-top padded-bottom"
+ >
+ <Connect(Component)
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ <Connect(HeaderMeta)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onWarningDismiss={[MockFunction]}
+ warnings={Array []}
+ />
+ </div>
+ <Connect(withAppState(Menu))
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isInProgress={false}
+ isPending={false}
+ onToggleProjectInfo={[Function]}
+ />
+ <InfoDrawer
+ displayed={false}
+ onClose={[Function]}
+ top={120}
+ >
+ <Connect(ProjectInformation)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onComponentChange={[MockFunction]}
+ />
+ </InfoDrawer>
+</ContextNavBar>
+`;
+
+exports[`renders correctly: has in progress notification 1`] = `
<ContextNavBar
height={102}
id="context-navigation"
Object {
"breadcrumbs": Array [
Object {
- "key": "component",
- "name": "component",
+ "key": "foo",
+ "name": "Foo",
"qualifier": "TRK",
},
],
- "key": "component",
- "name": "component",
- "organization": "org",
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
"qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
}
}
isInProgress={true}
- isPending={true}
+ isPending={false}
/>
}
>
Object {
"breadcrumbs": Array [
Object {
- "key": "component",
- "name": "component",
+ "key": "foo",
+ "name": "Foo",
"qualifier": "TRK",
},
],
- "key": "component",
- "name": "component",
- "organization": "org",
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
"qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
}
}
/>
Object {
"breadcrumbs": Array [
Object {
- "key": "component",
- "name": "component",
+ "key": "foo",
+ "name": "Foo",
"qualifier": "TRK",
},
],
- "key": "component",
- "name": "component",
- "organization": "org",
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
"qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
}
}
+ onWarningDismiss={[MockFunction]}
warnings={Array []}
/>
</div>
Object {
"breadcrumbs": Array [
Object {
- "key": "component",
- "name": "component",
+ "key": "foo",
+ "name": "Foo",
"qualifier": "TRK",
},
],
- "key": "component",
- "name": "component",
- "organization": "org",
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
"qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
}
}
isInProgress={true}
+ isPending={false}
+ onToggleProjectInfo={[Function]}
+ />
+ <InfoDrawer
+ displayed={false}
+ onClose={[Function]}
+ top={120}
+ >
+ <Connect(ProjectInformation)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onComponentChange={[MockFunction]}
+ />
+ </InfoDrawer>
+</ContextNavBar>
+`;
+
+exports[`renders correctly: has pending notification 1`] = `
+<ContextNavBar
+ height={102}
+ id="context-navigation"
+ notif={
+ <ComponentNavBgTaskNotif
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isInProgress={false}
+ isPending={true}
+ />
+ }
+>
+ <div
+ className="display-flex-center display-flex-space-between little-padded-top padded-bottom"
+ >
+ <Connect(Component)
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ <Connect(HeaderMeta)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onWarningDismiss={[MockFunction]}
+ warnings={Array []}
+ />
+ </div>
+ <Connect(withAppState(Menu))
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isInProgress={false}
isPending={true}
onToggleProjectInfo={[Function]}
/>
Object {
"breadcrumbs": Array [
Object {
- "key": "component",
- "name": "component",
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onComponentChange={[MockFunction]}
+ />
+ </InfoDrawer>
+</ContextNavBar>
+`;
+
+exports[`renders correctly: has warnings 1`] = `
+<ContextNavBar
+ height={72}
+ id="context-navigation"
+>
+ <div
+ className="display-flex-center display-flex-space-between little-padded-top"
+ >
+ <Connect(Component)
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
"qualifier": "TRK",
},
],
- "key": "component",
- "name": "component",
- "organization": "org",
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
"qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ />
+ <Connect(HeaderMeta)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ onWarningDismiss={[MockFunction]}
+ warnings={
+ Array [
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "Lorem ipsum",
+ },
+ ]
+ }
+ />
+ </div>
+ <Connect(withAppState(Menu))
+ branchLikes={Array []}
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ isInProgress={false}
+ isPending={false}
+ onToggleProjectInfo={[Function]}
+ />
+ <InfoDrawer
+ displayed={false}
+ onClose={[Function]}
+ top={120}
+ >
+ <Connect(ProjectInformation)
+ component={
+ Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "qualifier": "TRK",
+ },
+ ],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
}
}
onComponentChange={[MockFunction]}
/>
</Alert>
<AnalysisWarningsModal
+ componentKey="foo"
onClose={[Function]}
+ onWarningDismiss={[MockFunction]}
warnings={
Array [
- "warning 1",
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "warning 1",
+ },
]
}
/>
className="header-meta-warnings"
>
<ComponentNavWarnings
+ componentKey="my-project"
+ onWarningDismiss={[MockFunction]}
warnings={
Array [
- "ERROR_1",
- "ERROR_2",
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_1",
+ },
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_2",
+ },
]
}
/>
className="header-meta-warnings"
>
<ComponentNavWarnings
+ componentKey="my-project"
+ onWarningDismiss={[MockFunction]}
warnings={
Array [
- "ERROR_1",
- "ERROR_2",
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_1",
+ },
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_2",
+ },
]
}
/>
className="header-meta-warnings"
>
<ComponentNavWarnings
+ componentKey="foo"
+ onWarningDismiss={[MockFunction]}
warnings={
Array [
- "ERROR_1",
- "ERROR_2",
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_1",
+ },
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_2",
+ },
]
}
/>
className="header-meta-warnings"
>
<ComponentNavWarnings
+ componentKey="my-project"
+ onWarningDismiss={[MockFunction]}
warnings={
Array [
- "ERROR_1",
- "ERROR_2",
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_1",
+ },
+ Object {
+ "dismissable": false,
+ "key": "foo",
+ "message": "ERROR_2",
+ },
]
}
/>
}
.link-base-color {
- border-bottom: 1px solid #d0d0d0;
- color: var(--baseFontColor);
+ border-bottom: 1px solid #d0d0d0 !important;
+ color: var(--baseFontColor) !important;
}
.link-base-color:hover,
.link-base-color:active,
.link-base-color:focus {
- color: var(--blue);
+ color: var(--blue) !important;
}
.link-base-color:hover {
- border-bottom-color: var(--lightBlue);
+ border-bottom-color: var(--lightBlue) !important;
}
.link-base-color:active,
.link-base-color:focus {
- border-bottom-color: var(--lightBlue);
+ border-bottom-color: var(--lightBlue) !important;
}
.link-no-underline {
{this.state.stacktraceOpen && <Stacktrace onClose={this.closeStacktrace} task={task} />}
{this.state.warningsOpen && (
- <AnalysisWarningsModal onClose={this.closeWarnings} taskId={task.id} />
+ <AnalysisWarningsModal
+ componentKey={task.componentKey}
+ onClose={this.closeWarnings}
+ taskId={task.id}
+ />
)}
</td>
);
exports[`shows warnings 1`] = `
<AnalysisWarningsModal
+ componentKey="foo"
onClose={[Function]}
taskId="AXR8jg_0mF2ZsYr8Wzs2"
/>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { sanitize } from 'dompurify';
import * as React from 'react';
-import { ResetButtonLink } from 'sonar-ui-common/components/controls/buttons';
+import { ButtonLink } from 'sonar-ui-common/components/controls/buttons';
import Modal from 'sonar-ui-common/components/controls/Modal';
import WarningIcon from 'sonar-ui-common/components/icons/WarningIcon';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { getTask } from '../../api/ce';
+import { dismissAnalysisWarning, getTask } from '../../api/ce';
+import { TaskWarning } from '../../types/tasks';
+import { withCurrentUser } from '../hoc/withCurrentUser';
interface Props {
+ componentKey?: string;
+ currentUser: T.CurrentUser;
onClose: () => void;
+ onWarningDismiss?: () => void;
taskId?: string;
- warnings?: string[];
+ warnings?: TaskWarning[];
}
interface State {
loading: boolean;
- warnings: string[];
+ dismissedWarning?: string;
+ warnings: TaskWarning[];
}
-export default class AnalysisWarningsModal extends React.PureComponent<Props, State> {
+export class AnalysisWarningsModal extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
super(props);
- this.state = { loading: !props.warnings, warnings: props.warnings || [] };
+ this.state = {
+ loading: !props.warnings,
+ warnings: props.warnings || []
+ };
}
componentDidMount() {
this.mounted = false;
}
- loadWarnings(taskId: string) {
- this.setState({ loading: true });
- getTask(taskId, ['warnings']).then(
- ({ warnings = [] }) => {
- if (this.mounted) {
- this.setState({ loading: false, warnings });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
+ handleDismissMessage = async (messageKey: string) => {
+ const { componentKey } = this.props;
+
+ if (componentKey === undefined) {
+ return;
+ }
+
+ this.setState({ dismissedWarning: messageKey });
+
+ try {
+ await dismissAnalysisWarning(componentKey, messageKey);
+
+ if (this.props.onWarningDismiss) {
+ this.props.onWarningDismiss();
}
- );
- }
+ } catch (e) {
+ // Noop
+ }
+
+ if (this.mounted) {
+ this.setState({ dismissedWarning: undefined });
+ }
+ };
- keepLineBreaks = (warning: string) => {
- if (warning.includes('\n')) {
- const lines = warning.split('\n');
- return (
- <>
- {lines.map((line, index) => (
- <React.Fragment key={index}>
- {line}
- {index < lines.length - 1 && <br />}
- </React.Fragment>
- ))}
- </>
- );
- } else {
- return warning;
+ loadWarnings = async (taskId: string) => {
+ this.setState({ loading: true });
+ try {
+ const { warnings = [] } = await getTask(taskId, ['warnings']);
+
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ warnings: warnings.map(w => ({ key: w, message: w, dismissable: false }))
+ });
+ }
+ } catch (e) {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
}
};
render() {
+ const { currentUser } = this.props;
+ const { loading, dismissedWarning, warnings } = this.state;
+
const header = translate('warnings');
+
return (
<Modal contentLabel={header} onRequestClose={this.props.onClose}>
<header className="modal-head">
</header>
<div className="modal-body modal-container js-analysis-warnings">
- <DeferredSpinner loading={this.state.loading}>
- {this.state.warnings.map((warning, index) => (
- <div className="panel panel-vertical" key={index}>
+ <DeferredSpinner loading={loading}>
+ {warnings.map(({ dismissable, key, message }) => (
+ <div className="panel panel-vertical" key={key}>
<WarningIcon className="pull-left spacer-right" />
- <div className="overflow-hidden markdown">{this.keepLineBreaks(warning)}</div>
+ <div className="overflow-hidden markdown">
+ <span
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{
+ __html: sanitize(message.trim().replace(/\n/g, '<br />'), {
+ ALLOWED_ATTR: ['target', 'href']
+ })
+ }}
+ />
+
+ {dismissable && currentUser.isLoggedIn && (
+ <div className="spacer-top display-flex-inline">
+ <ButtonLink
+ className="link-base-color"
+ disabled={Boolean(dismissedWarning)}
+ onClick={() => {
+ this.handleDismissMessage(key);
+ }}>
+ {translate('dismiss_permanently')}
+ </ButtonLink>
+ {dismissedWarning === key && <i className="spinner spacer-left" />}
+ </div>
+ )}
+ </div>
</div>
))}
</DeferredSpinner>
</div>
<footer className="modal-foot">
- <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
+ <ButtonLink className="js-modal-close" onClick={this.props.onClose}>
{translate('close')}
- </ResetButtonLink>
+ </ButtonLink>
</footer>
</Modal>
);
}
}
+
+export default withCurrentUser(AnalysisWarningsModal);
import { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import { getTask } from '../../../api/ce';
-import AnalysisWarningsModal from '../AnalysisWarningsModal';
+import { dismissAnalysisWarning, getTask } from '../../../api/ce';
+import { mockTaskWarning } from '../../../helpers/mocks/tasks';
+import { mockCurrentUser, mockEvent } from '../../../helpers/testMocks';
+import { AnalysisWarningsModal } from '../AnalysisWarningsModal';
jest.mock('../../../api/ce', () => ({
+ dismissAnalysisWarning: jest.fn().mockResolvedValue(null),
getTask: jest.fn().mockResolvedValue({
warnings: ['message foo', 'message-bar', 'multiline message\nsecondline\n third line']
})
}));
-beforeEach(() => {
- (getTask as jest.Mock<any>).mockClear();
+beforeEach(jest.clearAllMocks);
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ warnings: [mockTaskWarning({ dismissable: true })] })).toMatchSnapshot(
+ 'with dismissable warnings'
+ );
+ expect(
+ shallowRender({
+ currentUser: mockCurrentUser({ isLoggedIn: false }),
+ warnings: [mockTaskWarning({ dismissable: true })]
+ })
+ ).toMatchSnapshot('do not show dismissable links for anonymous');
+});
+
+it('should not fetch task warnings if it does not have to', () => {
+ shallowRender();
+ expect(getTask).not.toBeCalled();
});
-it('should fetch warnings and render', async () => {
- const wrapper = shallow(<AnalysisWarningsModal onClose={jest.fn()} taskId="abcd1234" />);
+it('should fetch task warnings if it has to', async () => {
+ const wrapper = shallowRender({ taskId: 'abcd1234', warnings: undefined });
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
expect(getTask).toBeCalledWith('abcd1234', ['warnings']);
});
-it('should render warnings without fetch', () => {
- const wrapper = shallow(
- <AnalysisWarningsModal onClose={jest.fn()} warnings={['warning 1', 'warning 2']} />
- );
- expect(wrapper).toMatchSnapshot();
+it('should correctly handle dismissing warnings', () => {
+ return new Promise(resolve => {
+ const onWarningDismiss = jest.fn();
+ const wrapper = shallowRender({
+ componentKey: 'foo',
+ onWarningDismiss,
+ warnings: [mockTaskWarning({ key: 'bar', dismissable: true })]
+ });
+
+ const click = wrapper.find('ButtonLink.link-base-color').props().onClick;
+ if (click) {
+ click(mockEvent());
+
+ waitAndUpdate(wrapper).then(
+ () => {
+ expect(dismissAnalysisWarning).toBeCalledWith('foo', 'bar');
+ expect(onWarningDismiss).toBeCalled();
+ resolve();
+ },
+ () => {}
+ );
+ }
+ });
+});
+
+it('should correctly handle updates', async () => {
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+ expect(getTask).not.toBeCalled();
+
+ wrapper.setProps({ taskId: '1', warnings: undefined });
+ await waitAndUpdate(wrapper);
+ expect(getTask).toBeCalled();
+
+ (getTask as jest.Mock).mockClear();
+ wrapper.setProps({ taskId: undefined, warnings: [mockTaskWarning()] });
expect(getTask).not.toBeCalled();
});
+
+function shallowRender(props: Partial<AnalysisWarningsModal['props']> = {}) {
+ return shallow<AnalysisWarningsModal>(
+ <AnalysisWarningsModal
+ currentUser={mockCurrentUser({ isLoggedIn: true })}
+ onClose={jest.fn()}
+ warnings={[
+ mockTaskWarning({ message: 'warning 1' }),
+ mockTaskWarning({ message: 'warning 2' })
+ ]}
+ {...props}
+ />
+ );
+}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should fetch warnings and render 1`] = `
+exports[`should fetch task warnings if it has to 1`] = `
<Modal
contentLabel="warnings"
onRequestClose={[MockFunction]}
>
<div
className="panel panel-vertical"
- key="0"
+ key="message foo"
>
<WarningIcon
className="pull-left spacer-right"
<div
className="overflow-hidden markdown"
>
- message foo
+ <span
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "message foo",
+ }
+ }
+ />
</div>
</div>
<div
className="panel panel-vertical"
- key="1"
+ key="message-bar"
>
<WarningIcon
className="pull-left spacer-right"
<div
className="overflow-hidden markdown"
>
- message-bar
+ <span
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "message-bar",
+ }
+ }
+ />
</div>
</div>
<div
className="panel panel-vertical"
- key="2"
+ key="multiline message
+secondline
+ third line"
>
<WarningIcon
className="pull-left spacer-right"
<div
className="overflow-hidden markdown"
>
- multiline message
- <br />
- secondline
- <br />
- third line
+ <span
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "multiline message<br>secondline<br> third line",
+ }
+ }
+ />
</div>
</div>
</DeferredSpinner>
<footer
className="modal-foot"
>
- <ResetButtonLink
+ <ButtonLink
className="js-modal-close"
onClick={[MockFunction]}
>
close
- </ResetButtonLink>
+ </ButtonLink>
</footer>
</Modal>
`;
-exports[`should render warnings without fetch 1`] = `
+exports[`should render correctly: default 1`] = `
<Modal
contentLabel="warnings"
onRequestClose={[MockFunction]}
>
<div
className="panel panel-vertical"
- key="0"
+ key="foo"
>
<WarningIcon
className="pull-left spacer-right"
<div
className="overflow-hidden markdown"
>
- warning 1
+ <span
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "warning 1",
+ }
+ }
+ />
</div>
</div>
<div
className="panel panel-vertical"
- key="1"
+ key="foo"
>
<WarningIcon
className="pull-left spacer-right"
<div
className="overflow-hidden markdown"
>
- warning 2
+ <span
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "warning 2",
+ }
+ }
+ />
</div>
</div>
</DeferredSpinner>
<footer
className="modal-foot"
>
- <ResetButtonLink
+ <ButtonLink
className="js-modal-close"
onClick={[MockFunction]}
>
close
- </ResetButtonLink>
+ </ButtonLink>
+ </footer>
+</Modal>
+`;
+
+exports[`should render correctly: do not show dismissable links for anonymous 1`] = `
+<Modal
+ contentLabel="warnings"
+ onRequestClose={[MockFunction]}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ warnings
+ </h2>
+ </header>
+ <div
+ className="modal-body modal-container js-analysis-warnings"
+ >
+ <DeferredSpinner
+ loading={false}
+ >
+ <div
+ className="panel panel-vertical"
+ key="foo"
+ >
+ <WarningIcon
+ className="pull-left spacer-right"
+ />
+ <div
+ className="overflow-hidden markdown"
+ >
+ <span
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "Lorem ipsum",
+ }
+ }
+ />
+ </div>
+ </div>
+ </DeferredSpinner>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <ButtonLink
+ className="js-modal-close"
+ onClick={[MockFunction]}
+ >
+ close
+ </ButtonLink>
+ </footer>
+</Modal>
+`;
+
+exports[`should render correctly: with dismissable warnings 1`] = `
+<Modal
+ contentLabel="warnings"
+ onRequestClose={[MockFunction]}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ warnings
+ </h2>
+ </header>
+ <div
+ className="modal-body modal-container js-analysis-warnings"
+ >
+ <DeferredSpinner
+ loading={false}
+ >
+ <div
+ className="panel panel-vertical"
+ key="foo"
+ >
+ <WarningIcon
+ className="pull-left spacer-right"
+ />
+ <div
+ className="overflow-hidden markdown"
+ >
+ <span
+ dangerouslySetInnerHTML={
+ Object {
+ "__html": "Lorem ipsum",
+ }
+ }
+ />
+ <div
+ className="spacer-top display-flex-inline"
+ >
+ <ButtonLink
+ className="link-base-color"
+ disabled={false}
+ onClick={[Function]}
+ >
+ dismiss_permanently
+ </ButtonLink>
+ </div>
+ </div>
+ </div>
+ </DeferredSpinner>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <ButtonLink
+ className="js-modal-close"
+ onClick={[MockFunction]}
+ >
+ close
+ </ButtonLink>
</footer>
</Modal>
`;
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { ComponentQualifier } from '../../types/component';
-import { Task, TaskStatuses, TaskTypes } from '../../types/tasks';
+import { Task, TaskStatuses, TaskTypes, TaskWarning } from '../../types/tasks';
export function mockTask(overrides: Partial<Task> = {}): Task {
return {
...overrides
};
}
+
+export function mockTaskWarning(overrides: Partial<TaskWarning> = {}): TaskWarning {
+ return {
+ key: 'foo',
+ message: 'Lorem ipsum',
+ dismissable: false,
+ ...overrides
+ };
+}
warningCount?: number;
warnings?: string[];
}
+
+export interface TaskWarning {
+ key: string;
+ message: string;
+ dismissable: boolean;
+}
directories=Directories
directory=Directory
dismiss=Dismiss
+dismiss_permanently=Dismiss permanently
display=Display
download_verb=Download
duplications=Duplications