Commit ee1d9370 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '208755' into 'master'

Update icons in Sentry Error Tracking list for ignored/resolved errors

See merge request gitlab-org/gitlab!27125
parents dfeb0b6f 072a58ee
<script>
import { GlButton, GlIcon, GlButtonGroup, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
const IGNORED = 'ignored';
const RESOLVED = 'resolved';
const UNRESOLVED = 'unresolved';
const statusValidation = [IGNORED, RESOLVED, UNRESOLVED];
export default {
components: {
GlButton,
GlIcon,
GlButtonGroup,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
error: {
type: Object,
required: true,
validator: ({ status }) => statusValidation.includes(status),
},
},
computed: {
ignoreBtn() {
return this.error.status !== IGNORED
? { status: IGNORED, icon: 'eye-slash', title: __('Ignore') }
: { status: UNRESOLVED, icon: 'eye', title: __('Undo Ignore') };
},
resolveBtn() {
return this.error.status !== RESOLVED
? { status: RESOLVED, icon: 'check-circle', title: __('Resolve') }
: { status: UNRESOLVED, icon: 'canceled-circle', title: __('Unresolve') };
},
detailsLink() {
return `error_tracking/${this.error.id}/details`;
},
},
};
</script>
<template>
<div>
<gl-button-group class="flex-column flex-md-row ml-0 ml-md-n4">
<gl-button
:key="ignoreBtn.status"
:ref="`${ignoreBtn.title.toLowerCase()}Error`"
v-gl-tooltip.hover
class="d-block mb-2 mb-md-0 w-100"
:title="ignoreBtn.title"
@click="$emit('update-issue-status', { errorId: error.id, status: ignoreBtn.status })"
>
<gl-icon class="d-none d-md-inline m-0" :name="ignoreBtn.icon" :size="12" />
<span class="d-md-none">{{ ignoreBtn.title }}</span>
</gl-button>
<gl-button
:key="resolveBtn.status"
:ref="`${resolveBtn.title.toLowerCase()}Error`"
v-gl-tooltip.hover
class="d-block mb-2 mb-md-0 w-100"
:title="resolveBtn.title"
@click="$emit('update-issue-status', { errorId: error.id, status: resolveBtn.status })"
>
<gl-icon class="d-none d-md-inline m-0" :name="resolveBtn.icon" :size="12" />
<span class="d-md-none">{{ resolveBtn.title }}</span>
</gl-button>
</gl-button-group>
<gl-button
:href="detailsLink"
category="secondary"
variant="info"
class="d-block d-md-none mb-2 mb-md-0"
>
{{ __('More details') }}
</gl-button>
</div>
</template>
...@@ -13,12 +13,12 @@ import { ...@@ -13,12 +13,12 @@ import {
GlDropdownDivider, GlDropdownDivider,
GlTooltipDirective, GlTooltipDirective,
GlPagination, GlPagination,
GlButtonGroup,
} from '@gitlab/ui'; } from '@gitlab/ui';
import AccessorUtils from '~/lib/utils/accessor'; import AccessorUtils from '~/lib/utils/accessor';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import ErrorTrackingActions from './error_tracking_actions.vue';
export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center'; export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center';
...@@ -26,10 +26,6 @@ export default { ...@@ -26,10 +26,6 @@ export default {
FIRST_PAGE: 1, FIRST_PAGE: 1,
PREV_PAGE: 1, PREV_PAGE: 1,
NEXT_PAGE: 2, NEXT_PAGE: 2,
statusButtons: [
{ status: 'ignored', icon: 'eye-slash', title: __('Ignore') },
{ status: 'resolved', icon: 'check-circle', title: __('Resolve') },
],
fields: [ fields: [
{ {
key: 'error', key: 'error',
...@@ -58,12 +54,7 @@ export default { ...@@ -58,12 +54,7 @@ export default {
{ {
key: 'status', key: 'status',
label: '', label: '',
tdClass: `table-col d-none d-md-table-cell align-items-center pl-md-0`, tdClass: `${tableDataClass}`,
},
{
key: 'details',
tdClass: 'table-col d-md-none d-flex align-items-center rounded-bottom bg-secondary',
thClass: 'invisible w-0',
}, },
], ],
statusFilters: { statusFilters: {
...@@ -89,7 +80,7 @@ export default { ...@@ -89,7 +80,7 @@ export default {
GlFormInput, GlFormInput,
GlPagination, GlPagination,
TimeAgo, TimeAgo,
GlButtonGroup, ErrorTrackingActions,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -206,7 +197,7 @@ export default { ...@@ -206,7 +197,7 @@ export default {
this.filterValue = label; this.filterValue = label;
return this.filterByStatus(status); return this.filterByStatus(status);
}, },
updateIssueStatus(errorId, status) { updateIssueStatus({ errorId, status }) {
this.updateStatus({ this.updateStatus({
endpoint: this.getIssueUpdatePath(errorId), endpoint: this.getIssueUpdatePath(errorId),
status, status,
...@@ -220,8 +211,10 @@ export default { ...@@ -220,8 +211,10 @@ export default {
<template> <template>
<div class="error-list"> <div class="error-list">
<div v-if="errorTrackingEnabled"> <div v-if="errorTrackingEnabled">
<div class="row flex-column flex-sm-row align-items-sm-center row-top m-0 mt-sm-2 p-0 p-sm-3"> <div
<div class="search-box flex-fill mr-sm-2 my-3 m-sm-0 p-3 p-sm-0 bg-secondary"> class="row flex-column flex-md-row align-items-md-center m-0 mt-sm-2 p-3 p-sm-3 bg-secondary border"
>
<div class="search-box flex-fill mb-1 mb-md-0">
<div class="filtered-search-box mb-0"> <div class="filtered-search-box mb-0">
<gl-dropdown <gl-dropdown
:text="__('Recent searches')" :text="__('Recent searches')"
...@@ -273,7 +266,7 @@ export default { ...@@ -273,7 +266,7 @@ export default {
<gl-dropdown <gl-dropdown
:text="$options.statusFilters[statusFilter]" :text="$options.statusFilters[statusFilter]"
class="status-dropdown mr-2" class="status-dropdown mx-md-1 mb-1 mb-md-0"
menu-class="dropdown" menu-class="dropdown"
:disabled="loading" :disabled="loading"
> >
...@@ -366,46 +359,7 @@ export default { ...@@ -366,46 +359,7 @@ export default {
</div> </div>
</template> </template>
<template #cell(status)="errors"> <template #cell(status)="errors">
<gl-button-group> <error-tracking-actions :error="errors.item" @update-issue-status="updateIssueStatus" />
<gl-button
v-for="button in $options.statusButtons"
:key="button.status"
:ref="button.title.toLowerCase() + 'Error'"
v-gl-tooltip.hover
:title="button.title"
@click="updateIssueStatus(errors.item.id, button.status)"
>
<gl-icon :name="button.icon" :size="12" />
</gl-button>
</gl-button-group>
</template>
<template #cell(details)="errors">
<gl-button
category="primary"
variant="info"
block
class="mb-1 mt-2"
@click="updateIssueStatus(errors.item.id, 'resolved')"
>
{{ __('Resolve') }}
</gl-button>
<gl-button
category="secondary"
variant="default"
block
class="mb-2"
@click="updateIssueStatus(errors.item.id, 'ignored')"
>
{{ __('Ignore') }}
</gl-button>
<gl-button
:href="getDetailsLink(errors.item.id)"
category="secondary"
variant="info"
class="d-block mb-2"
>
{{ __('More details') }}
</gl-button>
</template> </template>
<template #empty> <template #empty>
{{ __('No errors to display.') }} {{ __('No errors to display.') }}
......
$gray-border: 1px solid $border-color;
.error-list { .error-list {
.sort-control { .sort-control {
.btn { .btn {
...@@ -13,19 +11,14 @@ $gray-border: 1px solid $border-color; ...@@ -13,19 +11,14 @@ $gray-border: 1px solid $border-color;
} }
} }
@include media-breakpoint-up(sm) { @include media-breakpoint-down(sm) {
.row-top {
border: $gray-border;
background-color: $gray-50;
}
}
@include media-breakpoint-down(md) {
.error-list-table { .error-list-table {
.table-col { .table-col {
min-height: 68px; min-height: 68px;
&:last-child { &:last-child {
background-color: $gray-normal;
&::before { &::before {
content: none !important; content: none !important;
} }
......
---
title: Update icons in Sentry Error Tracking list for ignored/resolved errors
merge_request: 27125
author:
type: other
...@@ -21333,6 +21333,9 @@ msgstr "" ...@@ -21333,6 +21333,9 @@ msgstr ""
msgid "Undo" msgid "Undo"
msgstr "" msgstr ""
msgid "Undo Ignore"
msgstr ""
msgid "Undo ignore" msgid "Undo ignore"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
describe('Error Tracking Actions', () => {
let wrapper;
function mountComponent(props) {
wrapper = shallowMount(ErrorTrackingActions, {
propsData: {
error: {
id: '1',
title: 'PG::ConnectionBad: FATAL',
type: 'error',
userCount: 0,
count: '52',
firstSeen: '2019-05-30T07:21:46Z',
lastSeen: '2019-11-06T03:21:39Z',
status: 'unresolved',
},
...props,
},
stubs: { GlButton },
});
}
beforeEach(() => {
mountComponent();
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
const findButtons = () => wrapper.findAll(GlButton);
describe('when error status is unresolved', () => {
it('renders the correct actions buttons to allow ignore and resolve', () => {
expect(findButtons().exists()).toBe(true);
return wrapper.vm.$nextTick().then(() => {
expect(
findButtons()
.at(0)
.attributes('title'),
).toBe('Ignore');
expect(
findButtons()
.at(1)
.attributes('title'),
).toBe('Resolve');
});
});
});
describe('when error status is ignored', () => {
beforeEach(() => {
mountComponent({ error: { status: 'ignored' } });
});
it('renders the correct action button to undo ignore', () => {
expect(findButtons().exists()).toBe(true);
return wrapper.vm.$nextTick().then(() => {
expect(
findButtons()
.at(0)
.attributes('title'),
).toBe('Undo Ignore');
});
});
});
describe('when error status is resolved', () => {
beforeEach(() => {
mountComponent({ error: { status: 'resolved' } });
});
it('renders the correct action button to undo unresolve', () => {
expect(findButtons().exists()).toBe(true);
return wrapper.vm.$nextTick().then(() => {
expect(
findButtons()
.at(1)
.attributes('title'),
).toBe('Unresolve');
});
});
});
});
...@@ -3,6 +3,7 @@ import Vuex from 'vuex'; ...@@ -3,6 +3,7 @@ import Vuex from 'vuex';
import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } from '@gitlab/ui'; import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } from '@gitlab/ui';
import stubChildren from 'helpers/stub_children'; import stubChildren from 'helpers/stub_children';
import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
import errorsList from './list_mock.json'; import errorsList from './list_mock.json';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -30,6 +31,7 @@ describe('ErrorTrackingList', () => { ...@@ -30,6 +31,7 @@ describe('ErrorTrackingList', () => {
.find(GlDropdown); .find(GlDropdown);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findPagination = () => wrapper.find(GlPagination); const findPagination = () => wrapper.find(GlPagination);
const findErrorActions = () => wrapper.find(ErrorTrackingActions);
function mountComponent({ function mountComponent({
errorTrackingEnabled = true, errorTrackingEnabled = true,
...@@ -151,15 +153,9 @@ describe('ErrorTrackingList', () => { ...@@ -151,15 +153,9 @@ describe('ErrorTrackingList', () => {
}); });
}); });
it('each error in the list should have an ignore button', () => { it('each error in the list should have an action button set', () => {
findErrorListRows().wrappers.forEach(row => { findErrorListRows().wrappers.forEach(row => {
expect(row.contains('glicon-stub[name="eye-slash"]')).toBe(true); expect(row.contains(ErrorTrackingActions)).toBe(true);
});
});
it('each error in the list should have a resolve button', () => {
findErrorListRows().wrappers.forEach(row => {
expect(row.contains('glicon-stub[name="check-circle"]')).toBe(true);
}); });
}); });
...@@ -237,8 +233,6 @@ describe('ErrorTrackingList', () => { ...@@ -237,8 +233,6 @@ describe('ErrorTrackingList', () => {
}); });
describe('When the ignore button on an error is clicked', () => { describe('When the ignore button on an error is clicked', () => {
const ignoreErrorButton = () => wrapper.find({ ref: 'ignoreError' });
beforeEach(() => { beforeEach(() => {
store.state.list.loading = false; store.state.list.loading = false;
store.state.list.errors = errorsList; store.state.list.errors = errorsList;
...@@ -253,7 +247,10 @@ describe('ErrorTrackingList', () => { ...@@ -253,7 +247,10 @@ describe('ErrorTrackingList', () => {
}); });
it('sends the "ignored" status and error ID', () => { it('sends the "ignored" status and error ID', () => {
ignoreErrorButton().trigger('click'); findErrorActions().vm.$emit('update-issue-status', {
errorId: errorsList[0].id,
status: 'ignored',
});
expect(actions.updateStatus).toHaveBeenCalledWith( expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
...@@ -265,7 +262,7 @@ describe('ErrorTrackingList', () => { ...@@ -265,7 +262,7 @@ describe('ErrorTrackingList', () => {
}); });
it('calls an action to remove the item from the list', () => { it('calls an action to remove the item from the list', () => {
ignoreErrorButton().trigger('click'); findErrorActions().vm.$emit('update-issue-status', { errorId: '1', status: undefined });
expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith( expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
'1', '1',
...@@ -275,8 +272,6 @@ describe('ErrorTrackingList', () => { ...@@ -275,8 +272,6 @@ describe('ErrorTrackingList', () => {
}); });
describe('When the resolve button on an error is clicked', () => { describe('When the resolve button on an error is clicked', () => {
const resolveErrorButton = () => wrapper.find({ ref: 'resolveError' });
beforeEach(() => { beforeEach(() => {
store.state.list.loading = false; store.state.list.loading = false;
store.state.list.errors = errorsList; store.state.list.errors = errorsList;
...@@ -291,7 +286,10 @@ describe('ErrorTrackingList', () => { ...@@ -291,7 +286,10 @@ describe('ErrorTrackingList', () => {
}); });
it('sends "resolved" status and error ID', () => { it('sends "resolved" status and error ID', () => {
resolveErrorButton().trigger('click'); findErrorActions().vm.$emit('update-issue-status', {
errorId: errorsList[0].id,
status: 'resolved',
});
expect(actions.updateStatus).toHaveBeenCalledWith( expect(actions.updateStatus).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
...@@ -303,7 +301,7 @@ describe('ErrorTrackingList', () => { ...@@ -303,7 +301,7 @@ describe('ErrorTrackingList', () => {
}); });
it('calls an action to remove the item from the list', () => { it('calls an action to remove the item from the list', () => {
resolveErrorButton().trigger('click'); findErrorActions().vm.$emit('update-issue-status', { errorId: '1', status: undefined });
expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith( expect(actions.removeIgnoredResolvedErrors).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
'1', '1',
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
"userCount": 0, "userCount": 0,
"count": "52", "count": "52",
"firstSeen": "2019-05-30T07:21:46Z", "firstSeen": "2019-05-30T07:21:46Z",
"lastSeen": "2019-11-06T03:21:39Z" "lastSeen": "2019-11-06T03:21:39Z",
"status": "unresolved"
}, },
{ {
"id": "2", "id": "2",
...@@ -15,7 +16,8 @@ ...@@ -15,7 +16,8 @@
"userCount": 0, "userCount": 0,
"count": "12", "count": "12",
"firstSeen": "2019-10-19T03:53:56Z", "firstSeen": "2019-10-19T03:53:56Z",
"lastSeen": "2019-11-05T03:51:54Z" "lastSeen": "2019-11-05T03:51:54Z",
"status": "unresolved"
}, },
{ {
"id": "3", "id": "3",
...@@ -24,6 +26,7 @@ ...@@ -24,6 +26,7 @@
"userCount": 0, "userCount": 0,
"count": "275", "count": "275",
"firstSeen": "2019-02-12T07:22:36Z", "firstSeen": "2019-02-12T07:22:36Z",
"lastSeen": "2019-10-22T03:20:48Z" "lastSeen": "2019-10-22T03:20:48Z",
"status": "unresolved"
} }
] ]
\ No newline at end of file
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