Commit 072a58ee authored by David O'Regan's avatar David O'Regan Committed by Natalia Tepluhina

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

parent 754f4ca7
<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 {
GlDropdownDivider,
GlTooltipDirective,
GlPagination,
GlButtonGroup,
} from '@gitlab/ui';
import AccessorUtils from '~/lib/utils/accessor';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale';
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';
......@@ -26,10 +26,6 @@ export default {
FIRST_PAGE: 1,
PREV_PAGE: 1,
NEXT_PAGE: 2,
statusButtons: [
{ status: 'ignored', icon: 'eye-slash', title: __('Ignore') },
{ status: 'resolved', icon: 'check-circle', title: __('Resolve') },
],
fields: [
{
key: 'error',
......@@ -58,12 +54,7 @@ export default {
{
key: 'status',
label: '',
tdClass: `table-col d-none d-md-table-cell align-items-center pl-md-0`,
},
{
key: 'details',
tdClass: 'table-col d-md-none d-flex align-items-center rounded-bottom bg-secondary',
thClass: 'invisible w-0',
tdClass: `${tableDataClass}`,
},
],
statusFilters: {
......@@ -89,7 +80,7 @@ export default {
GlFormInput,
GlPagination,
TimeAgo,
GlButtonGroup,
ErrorTrackingActions,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -206,7 +197,7 @@ export default {
this.filterValue = label;
return this.filterByStatus(status);
},
updateIssueStatus(errorId, status) {
updateIssueStatus({ errorId, status }) {
this.updateStatus({
endpoint: this.getIssueUpdatePath(errorId),
status,
......@@ -220,8 +211,10 @@ export default {
<template>
<div class="error-list">
<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 class="search-box flex-fill mr-sm-2 my-3 m-sm-0 p-3 p-sm-0 bg-secondary">
<div
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">
<gl-dropdown
:text="__('Recent searches')"
......@@ -273,7 +266,7 @@ export default {
<gl-dropdown
:text="$options.statusFilters[statusFilter]"
class="status-dropdown mr-2"
class="status-dropdown mx-md-1 mb-1 mb-md-0"
menu-class="dropdown"
:disabled="loading"
>
......@@ -366,46 +359,7 @@ export default {
</div>
</template>
<template #cell(status)="errors">
<gl-button-group>
<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>
<error-tracking-actions :error="errors.item" @update-issue-status="updateIssueStatus" />
</template>
<template #empty>
{{ __('No errors to display.') }}
......
$gray-border: 1px solid $border-color;
.error-list {
.sort-control {
.btn {
......@@ -13,19 +11,14 @@ $gray-border: 1px solid $border-color;
}
}
@include media-breakpoint-up(sm) {
.row-top {
border: $gray-border;
background-color: $gray-50;
}
}
@include media-breakpoint-down(md) {
@include media-breakpoint-down(sm) {
.error-list-table {
.table-col {
min-height: 68px;
&:last-child {
background-color: $gray-normal;
&::before {
content: none !important;
}
......
---
title: Update icons in Sentry Error Tracking list for ignored/resolved errors
merge_request: 27125
author:
type: other
......@@ -21310,6 +21310,9 @@ msgstr ""
msgid "Undo"
msgstr ""
msgid "Undo Ignore"
msgstr ""
msgid "Undo ignore"
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';
import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } from '@gitlab/ui';
import stubChildren from 'helpers/stub_children';
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';
const localVue = createLocalVue();
......@@ -30,6 +31,7 @@ describe('ErrorTrackingList', () => {
.find(GlDropdown);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findPagination = () => wrapper.find(GlPagination);
const findErrorActions = () => wrapper.find(ErrorTrackingActions);
function mountComponent({
errorTrackingEnabled = true,
......@@ -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 => {
expect(row.contains('glicon-stub[name="eye-slash"]')).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);
expect(row.contains(ErrorTrackingActions)).toBe(true);
});
});
......@@ -237,8 +233,6 @@ describe('ErrorTrackingList', () => {
});
describe('When the ignore button on an error is clicked', () => {
const ignoreErrorButton = () => wrapper.find({ ref: 'ignoreError' });
beforeEach(() => {
store.state.list.loading = false;
store.state.list.errors = errorsList;
......@@ -253,7 +247,10 @@ describe('ErrorTrackingList', () => {
});
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.anything(),
{
......@@ -265,7 +262,7 @@ describe('ErrorTrackingList', () => {
});
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.anything(),
'1',
......@@ -275,8 +272,6 @@ describe('ErrorTrackingList', () => {
});
describe('When the resolve button on an error is clicked', () => {
const resolveErrorButton = () => wrapper.find({ ref: 'resolveError' });
beforeEach(() => {
store.state.list.loading = false;
store.state.list.errors = errorsList;
......@@ -291,7 +286,10 @@ describe('ErrorTrackingList', () => {
});
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.anything(),
{
......@@ -303,7 +301,7 @@ describe('ErrorTrackingList', () => {
});
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.anything(),
'1',
......
......@@ -6,7 +6,8 @@
"userCount": 0,
"count": "52",
"firstSeen": "2019-05-30T07:21:46Z",
"lastSeen": "2019-11-06T03:21:39Z"
"lastSeen": "2019-11-06T03:21:39Z",
"status": "unresolved"
},
{
"id": "2",
......@@ -15,7 +16,8 @@
"userCount": 0,
"count": "12",
"firstSeen": "2019-10-19T03:53:56Z",
"lastSeen": "2019-11-05T03:51:54Z"
"lastSeen": "2019-11-05T03:51:54Z",
"status": "unresolved"
},
{
"id": "3",
......@@ -24,6 +26,7 @@
"userCount": 0,
"count": "275",
"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