Commit 50bea51f authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'Incident-highlight-bar' into 'master'

Incident highlight bar widget

See merge request gitlab-org/gitlab!41702
parents f31a1f4e 45457dfa
query getHighlightBarInfo($iid: String!, $fullPath: ID!) {
project(fullPath: $fullPath) {
issue(iid: $iid) {
alertManagementAlert {
title
detailsUrl
createdAt
eventCount
}
}
}
}
<script>
import { GlLink } from '@gitlab/ui';
import { formatDate } from '~/lib/utils/datetime_utility';
import getHighlightBarInfo from './graphql/queries/get_highlight_bar_info.graphql';
export default {
components: {
GlLink,
},
inject: ['fullPath', 'iid'],
apollo: {
alert: {
query: getHighlightBarInfo,
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
update: data => data.project?.issue?.alertManagementAlert,
},
},
computed: {
startTime() {
return formatDate(this.alert.createdAt, 'yyyy-mm-dd Z');
},
},
};
</script>
<template>
<div
v-if="alert"
class="gl-border-solid gl-border-1 gl-border-gray-100 gl-p-5 gl-mb-3 gl-rounded-base gl-display-flex gl-justify-content-space-between"
>
<div class="text-truncate gl-pr-3">
<span class="gl-font-weight-bold">{{ s__('HighlightBar|Original alert:') }}</span>
<gl-link :href="alert.detailsUrl">{{ alert.title }}</gl-link>
</div>
<div class="gl-pr-3 gl-white-space-nowrap">
<span class="gl-font-weight-bold">{{ s__('HighlightBar|Alert start time:') }}</span>
{{ startTime }}
</div>
<div class="gl-white-space-nowrap">
<span class="gl-font-weight-bold">{{ s__('HighlightBar|Alert events:') }}</span>
<span>{{ alert.eventCount }}</span>
</div>
</div>
</template>
<script> <script>
import { GlTab, GlTabs } from '@gitlab/ui'; import { GlTab, GlTabs } from '@gitlab/ui';
import DescriptionComponent from './description.vue'; import DescriptionComponent from '../description.vue';
import HighlightBar from './highlight_bar/higlight_bar.vue';
export default { export default {
components: { components: {
GlTab, GlTab,
GlTabs, GlTabs,
DescriptionComponent, DescriptionComponent,
HighlightBar,
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-tabs <gl-tabs content-class="gl-reset-line-height" class="gl-mt-n3" data-testid="incident-tabs">
content-class="gl-reset-line-height gl-mt-3"
class="gl-mt-n3"
data-testid="incident-tabs"
>
<gl-tab :title="__('Summary')"> <gl-tab :title="__('Summary')">
<highlight-bar />
<description-component v-bind="$attrs" /> <description-component v-bind="$attrs" />
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import issuableApp from './components/app.vue'; import issuableApp from './components/app.vue';
import incidentTabs from './components/incident_tabs.vue'; import incidentTabs from './components/incidents/incident_tabs.vue';
Vue.use(VueApollo);
export default function initIssuableApp(issuableData = {}) { export default function initIssuableApp(issuableData = {}) {
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const { projectNamespace, projectPath, iid } = issuableData;
return new Vue({ return new Vue({
el: document.getElementById('js-issuable-app'), el: document.getElementById('js-issuable-app'),
apolloProvider,
components: { components: {
issuableApp, issuableApp,
}, },
provide: {
fullPath: `${projectNamespace}/${projectPath}`,
iid,
},
render(createElement) { render(createElement) {
return createElement('issuable-app', { return createElement('issuable-app', {
props: { props: {
......
...@@ -294,7 +294,8 @@ module IssuablesHelper ...@@ -294,7 +294,8 @@ module IssuablesHelper
hasClosingMergeRequest: issuable.merge_requests_count(current_user) != 0, hasClosingMergeRequest: issuable.merge_requests_count(current_user) != 0,
issueType: issuable.issue_type, issueType: issuable.issue_type,
zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable), zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable),
sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier # rubocop:disable CodeReuse/ActiveRecord sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord
iid: issuable.iid.to_s
} }
end end
......
---
title: Incident highlight bar widget
merge_request: 41702
author:
type: added
...@@ -12855,6 +12855,15 @@ msgstr "" ...@@ -12855,6 +12855,15 @@ msgstr ""
msgid "Highest role:" msgid "Highest role:"
msgstr "" msgstr ""
msgid "HighlightBar|Alert events:"
msgstr ""
msgid "HighlightBar|Alert start time:"
msgstr ""
msgid "HighlightBar|Original alert:"
msgstr ""
msgid "History" msgid "History"
msgstr "" msgstr ""
......
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
secondRequest, secondRequest,
zoomMeetingUrl, zoomMeetingUrl,
} from '../mock_data'; } from '../mock_data';
import IncidentTabs from '~/issue_show/components/incident_tabs.vue'; import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
import DescriptionComponent from '~/issue_show/components/description.vue'; import DescriptionComponent from '~/issue_show/components/description.vue';
import PinnedLinks from '~/issue_show/components/pinned_links.vue'; import PinnedLinks from '~/issue_show/components/pinned_links.vue';
...@@ -39,6 +39,13 @@ describe('Issuable output', () => { ...@@ -39,6 +39,13 @@ describe('Issuable output', () => {
const mountComponent = (props = {}) => { const mountComponent = (props = {}) => {
wrapper = mount(IssuableApp, { wrapper = mount(IssuableApp, {
propsData: { ...appProps, ...props }, propsData: { ...appProps, ...props },
provide: {
fullPath: 'gitlab-org/incidents',
iid: '19',
},
stubs: {
HighlightBar: true,
},
}); });
}; };
......
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar/higlight_bar.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
jest.mock('~/lib/utils/datetime_utility');
describe('Highlight Bar', () => {
let wrapper;
const alert = {
createdAt: '2020-05-29T10:39:22Z',
detailsUrl: 'http://127.0.0.1:3000/root/unique-alerts/-/alert_management/1/details',
eventCount: 1,
title: 'Alert 1',
};
const mountComponent = () => {
wrapper = shallowMount(HighlightBar, {
provide: {
fullPath: 'project/id',
iid: '1',
},
data() {
return { alert };
},
});
};
beforeEach(() => {
mountComponent();
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
const findLink = () => wrapper.find(GlLink);
it('renders a link to the alert page', () => {
expect(findLink().exists()).toBe(true);
expect(findLink().attributes('href')).toBe(alert.detailsUrl);
expect(findLink().text()).toContain(alert.title);
});
it('renders formatted start time of the alert', () => {
const formattedDate = '2020-05-29 UTC';
formatDate.mockReturnValueOnce(formattedDate);
mountComponent();
expect(formatDate).toHaveBeenCalledWith(alert.createdAt, 'yyyy-mm-dd Z');
expect(wrapper.text()).toContain(formattedDate);
});
it('renders a number of alert events', () => {
expect(wrapper.text()).toContain(alert.eventCount);
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui'; import { GlTab } from '@gitlab/ui';
import IncidentTabs from '~/issue_show/components/incident_tabs.vue'; import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
import { descriptionProps } from '../mock_data'; import { descriptionProps } from '../mock_data';
import DescriptionComponent from '~/issue_show/components/description.vue'; import DescriptionComponent from '~/issue_show/components/description.vue';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar/higlight_bar.vue';
describe('Incident Tabs component', () => { describe('Incident Tabs component', () => {
let wrapper; let wrapper;
...@@ -25,6 +26,7 @@ describe('Incident Tabs component', () => { ...@@ -25,6 +26,7 @@ describe('Incident Tabs component', () => {
const findTabs = () => wrapper.findAll(GlTab); const findTabs = () => wrapper.findAll(GlTab);
const findSummaryTab = () => findTabs().at(0); const findSummaryTab = () => findTabs().at(0);
const findDescriptionComponent = () => wrapper.find(DescriptionComponent); const findDescriptionComponent = () => wrapper.find(DescriptionComponent);
const findHighlightBarComponent = () => wrapper.find(HighlightBar);
describe('default state', () => { describe('default state', () => {
it('renders the summary tab', async () => { it('renders the summary tab', async () => {
...@@ -33,8 +35,9 @@ describe('Incident Tabs component', () => { ...@@ -33,8 +35,9 @@ describe('Incident Tabs component', () => {
expect(findSummaryTab().attributes('title')).toBe('Summary'); expect(findSummaryTab().attributes('title')).toBe('Summary');
}); });
it('renders the description component', () => { it('renders the description component with highlight bar', () => {
expect(findDescriptionComponent().exists()).toBe(true); expect(findDescriptionComponent().exists()).toBe(true);
expect(findHighlightBarComponent().exists()).toBe(true);
}); });
it('passes all props to the description component', () => { it('passes all props to the description component', () => {
......
...@@ -198,7 +198,7 @@ RSpec.describe IssuablesHelper do ...@@ -198,7 +198,7 @@ RSpec.describe IssuablesHelper do
initialDescriptionHtml: '<p dir="auto">issue text</p>', initialDescriptionHtml: '<p dir="auto">issue text</p>',
initialDescriptionText: 'issue text', initialDescriptionText: 'issue text',
initialTaskStatus: '0 of 0 tasks completed', initialTaskStatus: '0 of 0 tasks completed',
issueType: 'issue' iid: issue.iid.to_s
} }
expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data)) expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment