diff options
author | Simon Knox <simon@gitlab.com> | 2019-01-11 22:52:26 +0000 |
---|---|---|
committer | Clement Ho <clemmakesapps@gmail.com> | 2019-01-11 22:52:26 +0000 |
commit | 5a5212542aeac8c3b2e05e853e2e709c7141a999 (patch) | |
tree | 9c92bbb6f3c619c69dd225ec32350f59883bba32 /app | |
parent | 6d6c2e95dd8c813a9646c6289589957749bd4b0c (diff) | |
download | gitlab-ce-5a5212542aeac8c3b2e05e853e2e709c7141a999.tar.gz |
List Sentry Errors in GitLab - Frontend
Diffstat (limited to 'app')
12 files changed, 257 insertions, 0 deletions
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue new file mode 100644 index 00000000000..6981afe1ead --- /dev/null +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -0,0 +1,118 @@ +<script> +import { mapActions, mapState } from 'vuex'; +import { GlEmptyState, GlButton, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui'; +import Icon from '~/vue_shared/components/icon.vue'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import { __ } from '~/locale'; + +export default { + fields: [ + { key: 'error', label: __('Open errors') }, + { key: 'events', label: __('Events') }, + { key: 'users', label: __('Users') }, + { key: 'lastSeen', label: __('Last seen') }, + ], + components: { + GlEmptyState, + GlButton, + GlLink, + GlLoadingIcon, + GlTable, + Icon, + TimeAgo, + }, + props: { + indexPath: { + type: String, + required: true, + }, + enableErrorTrackingLink: { + type: String, + required: true, + }, + errorTrackingEnabled: { + type: Boolean, + required: true, + }, + illustrationPath: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(['errors', 'externalUrl', 'loading']), + }, + created() { + if (this.errorTrackingEnabled) { + this.startPolling(this.indexPath); + } + }, + methods: { + ...mapActions(['startPolling']), + }, +}; +</script> + +<template> + <div> + <div v-if="errorTrackingEnabled"> + <div v-if="loading" class="py-3"><gl-loading-icon :size="3" /></div> + <div v-else> + <div class="d-flex justify-content-end"> + <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank" + >View in Sentry <icon name="external-link" /> + </gl-button> + </div> + <gl-table + :items="errors" + :fields="$options.fields" + :show-empty="true" + :empty-text="__('No errors to display')" + > + <template slot="HEAD_events" slot-scope="data"> + <div class="text-right">{{ data.label }}</div> + </template> + <template slot="HEAD_users" slot-scope="data"> + <div class="text-right">{{ data.label }}</div> + </template> + <template slot="error" slot-scope="errors"> + <div class="d-flex flex-column"> + <div class="d-flex"> + <gl-link :href="errors.item.externalUrl" class="d-flex text-dark" target="_blank"> + <strong>{{ errors.item.title.trim() }}</strong> + <icon name="external-link" class="ml-1" /> + </gl-link> + <span class="text-secondary ml-2">{{ errors.item.culprit }}</span> + </div> + {{ errors.item.message || __('No details available') }} + </div> + </template> + + <template slot="events" slot-scope="errors"> + <div class="text-right">{{ errors.item.count }}</div> + </template> + + <template slot="users" slot-scope="errors"> + <div class="text-right">{{ errors.item.userCount }}</div> + </template> + + <template slot="lastSeen" slot-scope="errors"> + <div class="d-flex align-items-center"> + <icon name="calendar" css-classes="text-secondary mr-1" /> + <time-ago :time="errors.item.lastSeen" class="text-secondary" /> + </div> + </template> + </gl-table> + </div> + </div> + <div v-else> + <gl-empty-state + :title="__('Get started with error tracking')" + :description="__('Monitor your errors by integrating with Sentry')" + :primary-button-text="__('Enable error tracking')" + :primary-button-link="enableErrorTrackingLink" + :svg-path="illustrationPath" + /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/error_tracking/index.js b/app/assets/javascripts/error_tracking/index.js new file mode 100644 index 00000000000..808ae2c9a41 --- /dev/null +++ b/app/assets/javascripts/error_tracking/index.js @@ -0,0 +1,35 @@ +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import store from './store'; +import ErrorTrackingList from './components/error_tracking_list.vue'; + +export default () => { + if (!gon.features.errorTracking) { + return; + } + + // eslint-disable-next-line no-new + new Vue({ + el: '#js-error_tracking', + components: { + ErrorTrackingList, + }, + store, + render(createElement) { + const domEl = document.querySelector(this.$options.el); + const { indexPath, enableErrorTrackingLink, illustrationPath } = domEl.dataset; + let { errorTrackingEnabled } = domEl.dataset; + + errorTrackingEnabled = parseBoolean(errorTrackingEnabled); + + return createElement('error-tracking-list', { + props: { + indexPath, + enableErrorTrackingLink, + errorTrackingEnabled, + illustrationPath, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/error_tracking/services/index.js b/app/assets/javascripts/error_tracking/services/index.js new file mode 100644 index 00000000000..ab89521dc46 --- /dev/null +++ b/app/assets/javascripts/error_tracking/services/index.js @@ -0,0 +1,7 @@ +import axios from '~/lib/utils/axios_utils'; + +export default { + getErrorList({ endpoint }) { + return axios.get(endpoint); + }, +}; diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js new file mode 100644 index 00000000000..2e192c958ba --- /dev/null +++ b/app/assets/javascripts/error_tracking/store/actions.js @@ -0,0 +1,31 @@ +import Service from '../services'; +import * as types from './mutation_types'; +import createFlash from '~/flash'; +import Poll from '~/lib/utils/poll'; +import { __ } from '~/locale'; + +let eTagPoll; + +export function startPolling({ commit }, endpoint) { + eTagPoll = new Poll({ + resource: Service, + method: 'getErrorList', + data: { endpoint }, + successCallback: ({ data }) => { + if (!data) { + return; + } + commit(types.SET_ERRORS, data.errors); + commit(types.SET_EXTERNAL_URL, data.external_url); + commit(types.SET_LOADING, false); + }, + errorCallback: () => { + commit(types.SET_LOADING, false); + createFlash(__('Failed to load errors from Sentry')); + }, + }); + + eTagPoll.makeRequest(); +} + +export default () => {}; diff --git a/app/assets/javascripts/error_tracking/store/index.js b/app/assets/javascripts/error_tracking/store/index.js new file mode 100644 index 00000000000..3136682fb64 --- /dev/null +++ b/app/assets/javascripts/error_tracking/store/index.js @@ -0,0 +1,19 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import * as actions from './actions'; +import mutations from './mutations'; + +Vue.use(Vuex); + +export const createStore = () => + new Vuex.Store({ + state: { + errors: [], + externalUrl: '', + loading: true, + }, + actions, + mutations, + }); + +export default createStore(); diff --git a/app/assets/javascripts/error_tracking/store/mutation_types.js b/app/assets/javascripts/error_tracking/store/mutation_types.js new file mode 100644 index 00000000000..f9d77a6b08e --- /dev/null +++ b/app/assets/javascripts/error_tracking/store/mutation_types.js @@ -0,0 +1,3 @@ +export const SET_ERRORS = 'SET_ERRORS'; +export const SET_EXTERNAL_URL = 'SET_EXTERNAL_URL'; +export const SET_LOADING = 'SET_LOADING'; diff --git a/app/assets/javascripts/error_tracking/store/mutations.js b/app/assets/javascripts/error_tracking/store/mutations.js new file mode 100644 index 00000000000..e4bd81db9c9 --- /dev/null +++ b/app/assets/javascripts/error_tracking/store/mutations.js @@ -0,0 +1,14 @@ +import * as types from './mutation_types'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +export default { + [types.SET_ERRORS](state, data) { + state.errors = convertObjectPropsToCamelCase(data, { deep: true }); + }, + [types.SET_EXTERNAL_URL](state, url) { + state.externalUrl = url; + }, + [types.SET_LOADING](state, loading) { + state.loading = loading; + }, +}; diff --git a/app/assets/javascripts/pages/projects/error_tracking/index.js b/app/assets/javascripts/pages/projects/error_tracking/index.js new file mode 100644 index 00000000000..5a8fe137e9a --- /dev/null +++ b/app/assets/javascripts/pages/projects/error_tracking/index.js @@ -0,0 +1,5 @@ +import ErrorTracking from '~/error_tracking'; + +document.addEventListener('DOMContentLoaded', () => { + ErrorTracking(); +}); diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb new file mode 100644 index 00000000000..6daf2e21ca2 --- /dev/null +++ b/app/helpers/projects/error_tracking_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Projects::ErrorTrackingHelper + def error_tracking_data(project) + error_tracking_enabled = !!project.error_tracking_setting&.enabled? + + { + 'index-path' => project_error_tracking_index_path(project, + format: :json), + 'enable-error-tracking-link' => project_settings_operations_path(project), + 'error-tracking-enabled' => error_tracking_enabled.to_s, + 'illustration-path' => image_path('illustrations/cluster_popover.svg') + } + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e67c327f7f8..ebbed08f78a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -335,6 +335,7 @@ module ProjectsHelper builds: :read_build, clusters: :read_cluster, serverless: :read_cluster, + error_tracking: :read_sentry_issue, labels: :read_label, issues: :read_issue, project_members: :read_project_member, @@ -579,6 +580,7 @@ module ProjectsHelper environments clusters functions + error_tracking user gcp ] diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index c8fdc0112b4..d62cbc1684b 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -227,6 +227,12 @@ %span = _('Environments') + - if project_nav_tab?(:error_tracking) && Feature.enabled?(:error_tracking, @project) + = nav_link(controller: :error_tracking) do + = link_to project_error_tracking_index_path(@project), title: _('Error Tracking'), class: 'shortcuts-tracking qa-operations-tracking-link' do + %span + = _('Error Tracking') + - if project_nav_tab? :serverless = nav_link(controller: :functions) do = link_to project_serverless_functions_path(@project), title: _('Serverless') do diff --git a/app/views/projects/error_tracking/index.html.haml b/app/views/projects/error_tracking/index.html.haml index a3e0dc75f6f..bc02c5f0e5a 100644 --- a/app/views/projects/error_tracking/index.html.haml +++ b/app/views/projects/error_tracking/index.html.haml @@ -1 +1,3 @@ - page_title _('Errors') + +#js-error_tracking{ data: error_tracking_data(@project) } |