diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-26 18:08:17 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-26 18:08:17 +0300 |
commit | c9d79ef3b5b67792e331a4cc8e6325f3b4a04760 (patch) | |
tree | 0b7c48c03e5c59362f975c20fcf84635fddd94e6 /app | |
parent | 1691cbe307f7698b3ee39811278990c43b6751a5 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
19 files changed, 270 insertions, 42 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index bb9f092a9ae..c08b4fb2f63 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -18,10 +18,15 @@ import query from '../graphql/queries/details.query.graphql'; import { fetchPolicies } from '~/lib/graphql'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { ALERTS_SEVERITY_LABELS } from '../constants'; +import { + ALERTS_SEVERITY_LABELS, + trackAlertsDetailsViewsOptions, + trackAlertStatusUpdateOptions, +} from '../constants'; import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; +import Tracking from '~/tracking'; export default { statuses: { @@ -108,6 +113,9 @@ export default { return this.errored && !this.isErrorDismissed; }, }, + mounted() { + this.trackPageViews(); + }, methods: { dismissError() { this.isErrorDismissed = true; @@ -122,6 +130,9 @@ export default { projectPath: this.projectPath, }, }) + .then(() => { + this.trackStatusUpdate(status); + }) .catch(() => { createFlash( s__( @@ -157,6 +168,14 @@ export default { issuePath(issueId) { return joinPaths(this.projectIssuesPath, issueId); }, + trackPageViews() { + const { category, action } = trackAlertsDetailsViewsOptions; + Tracking.event(category, action); + }, + trackStatusUpdate(status) { + const { category, action, label } = trackAlertStatusUpdateOptions; + Tracking.event(category, action, { label, property: status }); + }, }, }; </script> diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue index a1a71e592f6..74ce76739a2 100644 --- a/app/assets/javascripts/alert_management/components/alert_management_list.vue +++ b/app/assets/javascripts/alert_management/components/alert_management_list.vue @@ -19,13 +19,20 @@ import { fetchPolicies } from '~/lib/graphql'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import getAlerts from '../graphql/queries/get_alerts.query.graphql'; import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql'; -import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants'; +import { + ALERTS_STATUS, + ALERTS_STATUS_TABS, + ALERTS_SEVERITY_LABELS, + trackAlertListViewsOptions, + trackAlertStatusUpdateOptions, +} from '../constants'; import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import { capitalizeFirstCharacter, convertToSnakeCase } from '~/lib/utils/text_utility'; +import Tracking from '~/tracking'; const tdClass = 'table-col d-flex d-md-table-cell align-items-center'; const bodyTrClass = - 'gl-border-1 gl-border-t-solid gl-border-gray-100 hover-bg-blue-50 hover-gl-cursor-pointer hover-gl-border-b-solid hover-gl-border-blue-200'; + 'gl-border-1 gl-border-t-solid gl-border-gray-100 gl-hover-bg-blue-50 gl-hover-cursor-pointer gl-hover-border-b-solid gl-hover-border-blue-200'; const findDefaultSortColumn = () => document.querySelector('.js-started-at'); export default { @@ -182,6 +189,7 @@ export default { }, mounted() { findDefaultSortColumn().ariaSort = 'ascending'; + this.trackPageViews(); }, methods: { filterAlertsByStatus(tabIndex) { @@ -208,6 +216,7 @@ export default { }, }) .then(() => { + this.trackStatusUpdate(status); this.$apollo.queries.alerts.refetch(); this.$apollo.queries.alertsCount.refetch(); }) @@ -222,6 +231,14 @@ export default { navigateToAlertDetails({ iid }) { return visitUrl(joinPaths(window.location.pathname, iid, 'details')); }, + trackPageViews() { + const { category, action } = trackAlertListViewsOptions; + Tracking.event(category, action); + }, + trackStatusUpdate(status) { + const { category, action, label } = trackAlertStatusUpdateOptions; + Tracking.event(category, action, { label, property: status }); + }, }, }; </script> diff --git a/app/assets/javascripts/alert_management/constants.js b/app/assets/javascripts/alert_management/constants.js index 9df01d9d0b5..a8f5d6cfe30 100644 --- a/app/assets/javascripts/alert_management/constants.js +++ b/app/assets/javascripts/alert_management/constants.js @@ -44,3 +44,30 @@ export const ALERTS_STATUS_TABS = [ filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED, ALERTS_STATUS.RESOLVED], }, ]; + +/* eslint-disable @gitlab/require-i18n-strings */ + +/** + * Tracks snowplow event when user views alerts list + */ +export const trackAlertListViewsOptions = { + category: 'Alert Management', + action: 'view_alerts_list', +}; + +/** + * Tracks snowplow event when user views alert details + */ +export const trackAlertsDetailsViewsOptions = { + category: 'Alert Management', + action: 'view_alert_details', +}; + +/** + * Tracks snowplow event when alert status is updated + */ +export const trackAlertStatusUpdateOptions = { + category: 'Alert Management', + action: 'update_alert_status', + label: 'Status', +}; diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 878f49cc6be..02fd0334403 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -60,13 +60,11 @@ class ListIssue { } removeAssignee(removeAssignee) { - if (removeAssignee) { - this.assignees = this.assignees.filter(assignee => assignee.id !== removeAssignee.id); - } + boardsStore.removeIssueAssignee(this, removeAssignee); } removeAllAssignees() { - this.assignees = []; + boardsStore.removeAllIssueAssignees(this); } addMilestone(milestone) { diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index fdbd7e89bfb..feef405d52e 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -682,10 +682,20 @@ const boardsStore = { ...this.multiSelect.list.slice(index + 1), ]; }, + removeIssueAssignee(issue, removeAssignee) { + if (removeAssignee) { + issue.assignees = issue.assignees.filter(assignee => assignee.id !== removeAssignee.id); + } + }, clearMultiSelect() { this.multiSelect.list = []; }, + + removeAllIssueAssignees(issue) { + issue.assignees = []; + }, + refreshIssueData(issue, obj) { issue.id = obj.id; issue.iid = obj.iid; diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue index 148edfe3a51..da079877c72 100644 --- a/app/assets/javascripts/error_tracking/components/error_details.vue +++ b/app/assets/javascripts/error_tracking/components/error_details.vue @@ -20,8 +20,13 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import Stacktrace from './stacktrace.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; import timeagoMixin from '~/vue_shared/mixins/timeago'; -import { trackClickErrorLinkToSentryOptions } from '../utils'; import { severityLevel, severityLevelVariant, errorStatus } from './constants'; +import Tracking from '~/tracking'; +import { + trackClickErrorLinkToSentryOptions, + trackErrorDetailsViewsOptions, + trackErrorStatusUpdateOptions, +} from '../utils'; import query from '../queries/details.query.graphql'; @@ -172,6 +177,7 @@ export default { }, }, mounted() { + this.trackPageViews(); this.startPollingStacktrace(this.issueStackTracePath); this.errorPollTimeout = Date.now() + SENTRY_TIMEOUT; this.$apollo.queries.error.setOptions({ @@ -194,7 +200,10 @@ export default { onIgnoreStatusUpdate() { const status = this.errorStatus === errorStatus.IGNORED ? errorStatus.UNRESOLVED : errorStatus.IGNORED; - this.updateIgnoreStatus({ endpoint: this.issueUpdatePath, status }); + // eslint-disable-next-line promise/catch-or-return + this.updateIgnoreStatus({ endpoint: this.issueUpdatePath, status }).then(() => { + this.trackStatusUpdate(status); + }); }, onResolveStatusUpdate() { const status = @@ -206,6 +215,7 @@ export default { if (this.closedIssueId) { this.isAlertVisible = true; } + this.trackStatusUpdate(status); }); }, onNoApolloResult() { @@ -218,6 +228,14 @@ export default { formatDate(date) { return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`; }, + trackPageViews() { + const { category, action } = trackErrorDetailsViewsOptions; + Tracking.event(category, action); + }, + trackStatusUpdate(status) { + const { category, action, label } = trackErrorStatusUpdateOptions; + Tracking.event(category, action, { label, property: status }); + }, }, }; </script> @@ -259,7 +277,7 @@ export default { <div class="d-inline-flex bv-d-sm-down-none"> <gl-deprecated-button :loading="updatingIgnoreStatus" - data-qa-selector="update_ignore_status_button" + data-testid="update-ignore-status-btn" @click="onIgnoreStatusUpdate" > {{ ignoreBtnLabel }} @@ -267,7 +285,7 @@ export default { <gl-deprecated-button class="btn-outline-info ml-2" :loading="updatingResolveStatus" - data-qa-selector="update_resolve_status_button" + data-testid="update-resolve-status-btn" @click="onResolveStatusUpdate" > {{ resolveBtnLabel }} @@ -275,7 +293,7 @@ export default { <gl-deprecated-button v-if="error.gitlabIssuePath" class="ml-2" - data-qa-selector="view_issue_button" + data-testid="view_issue_button" :href="error.gitlabIssuePath" variant="success" > @@ -375,6 +393,7 @@ export default { v-track-event="trackClickErrorLinkToSentryOptions(error.externalUrl)" :href="error.externalUrl" target="_blank" + data-testid="external-url-link" > <span class="text-truncate">{{ error.externalUrl }}</span> <icon name="external-link" class="ml-1 flex-shrink-0" /> diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index 45432e8ebd8..111b5ad60a5 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -19,6 +19,8 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { __ } from '~/locale'; import { isEmpty } from 'lodash'; import ErrorTrackingActions from './error_tracking_actions.vue'; +import Tracking from '~/tracking'; +import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '../utils'; export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center'; @@ -150,6 +152,9 @@ export default { this.startPolling(); } }, + mounted() { + this.trackPageViews(); + }, methods: { ...mapActions('list', [ 'startPolling', @@ -197,13 +202,25 @@ export default { this.filterValue = label; return this.filterByStatus(status); }, - updateIssueStatus({ errorId, status }) { + updateErrosStatus({ errorId, status }) { + // eslint-disable-next-line promise/catch-or-return this.updateStatus({ endpoint: this.getIssueUpdatePath(errorId), status, + }).then(() => { + this.trackStatusUpdate(status); }); + this.removeIgnoredResolvedErrors(errorId); }, + trackPageViews() { + const { category, action } = trackErrorListViewsOptions; + Tracking.event(category, action); + }, + trackStatusUpdate(status) { + const { category, action, label } = trackErrorStatusUpdateOptions; + Tracking.event(category, action, { label, property: status }); + }, }, }; </script> @@ -359,7 +376,7 @@ export default { </div> </template> <template #cell(status)="errors"> - <error-tracking-actions :error="errors.item" @update-issue-status="updateIssueStatus" /> + <error-tracking-actions :error="errors.item" @update-issue-status="updateErrosStatus" /> </template> <template #empty> {{ __('No errors to display.') }} diff --git a/app/assets/javascripts/error_tracking/utils.js b/app/assets/javascripts/error_tracking/utils.js index d1cd70a72fa..e519b8ebfe5 100644 --- a/app/assets/javascripts/error_tracking/utils.js +++ b/app/assets/javascripts/error_tracking/utils.js @@ -1,4 +1,4 @@ -/* eslint-disable @gitlab/require-i18n-strings, import/prefer-default-export */ +/* eslint-disable @gitlab/require-i18n-strings */ /** * Tracks snowplow event when User clicks on error link to Sentry @@ -10,3 +10,28 @@ export const trackClickErrorLinkToSentryOptions = url => ({ label: 'Error Link', property: url, }); + +/** + * Tracks snowplow event when user views error list + */ +export const trackErrorListViewsOptions = { + category: 'Error Tracking', + action: 'view_errors_list', +}; + +/** + * Tracks snowplow event when user views error details + */ +export const trackErrorDetailsViewsOptions = { + category: 'Error Tracking', + action: 'view_error_details', +}; + +/** + * Tracks snowplow event when error status is updated + */ +export const trackErrorStatusUpdateOptions = { + category: 'Error Tracking', + action: 'update_error_status', + label: 'Status', +}; diff --git a/app/assets/javascripts/projects/commits/components/author_select.vue b/app/assets/javascripts/projects/commits/components/author_select.vue index eb514b5c070..a8589b50899 100644 --- a/app/assets/javascripts/projects/commits/components/author_select.vue +++ b/app/assets/javascripts/projects/commits/components/author_select.vue @@ -110,8 +110,8 @@ export default { <gl-new-dropdown :text="dropdownText" :disabled="hasSearchParam" - toggle-class="gl-py-3" - class="gl-dropdown w-100 mt-2 mt-sm-0" + toggle-class="gl-py-3 gl-border-0" + class="w-100 mt-2 mt-sm-0" > <gl-new-dropdown-header> {{ __('Search by author') }} diff --git a/app/assets/stylesheets/pages/storage_quota.scss b/app/assets/stylesheets/pages/storage_quota.scss new file mode 100644 index 00000000000..97ae4f0ade4 --- /dev/null +++ b/app/assets/stylesheets/pages/storage_quota.scss @@ -0,0 +1,23 @@ +.storage-type-usage { + &:first-child { + @include gl-rounded-top-left-base; + @include gl-rounded-bottom-left-base; + } + + &:last-child { + @include gl-rounded-top-right-base; + @include gl-rounded-bottom-right-base; + } + + &:not(:first-child) { + @include gl-border-l-1; + @include gl-border-l-solid; + @include gl-border-white; + } + + &:not(:last-child) { + @include gl-border-r-1; + @include gl-border-r-solid; + @include gl-border-white; + } +} diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 2b51bdd825f..803eac52317 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -87,21 +87,5 @@ } } -.gl-shim-h-2 { - height: px-to-rem(4px); -} - -.gl-shim-w-5 { - width: px-to-rem(16px); -} - -.gl-shim-pb-3 { - padding-bottom: 8px; -} - -.gl-shim-pt-5 { - padding-top: 16px; -} - .gl-text-purple { color: $purple; } .gl-bg-purple-light { background-color: $purple-light; } diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index d4b0d3b2674..d3dfb1813e4 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -13,9 +13,7 @@ module NotesActions end def index - current_fetched_at = Time.current.to_i - - notes_json = { notes: [], last_fetched_at: current_fetched_at } + notes_json = { notes: [], last_fetched_at: Time.current.to_i } notes = notes_finder .execute @@ -24,7 +22,7 @@ module NotesActions if notes_filter != UserPreference::NOTES_FILTERS[:only_comments] notes = ResourceEvents::MergeIntoNotesService - .new(noteable, current_user, last_fetched_at: current_fetched_at) + .new(noteable, current_user, last_fetched_at: last_fetched_at) .execute(notes) end diff --git a/app/finders/resource_milestone_event_finder.rb b/app/finders/resource_milestone_event_finder.rb new file mode 100644 index 00000000000..7af34f0a4bc --- /dev/null +++ b/app/finders/resource_milestone_event_finder.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +class ResourceMilestoneEventFinder + include FinderMethods + + MAX_PER_PAGE = 100 + + attr_reader :params, :current_user, :eventable + + def initialize(current_user, eventable, params = {}) + @current_user = current_user + @eventable = eventable + @params = params + end + + def execute + Kaminari.paginate_array(visible_events) + end + + private + + def visible_events + @visible_events ||= visible_to_user(events) + end + + def events + @events ||= eventable.resource_milestone_events.include_relations.page(page).per(per_page) + end + + def visible_to_user(events) + events.select { |event| visible_for_user?(event) } + end + + def visible_for_user?(event) + milestone = event_milestones[event.milestone_id] + return if milestone.blank? + + parent = milestone.parent + parent_availabilities[key_for_parent(parent)] + end + + def parent_availabilities + @parent_availabilities ||= relevant_parents.to_h do |parent| + [key_for_parent(parent), Ability.allowed?(current_user, :read_milestone, parent)] + end + end + + def key_for_parent(parent) + "#{parent.class.name}_#{parent.id}" + end + + def event_milestones + @milestones ||= events.map(&:milestone).uniq.to_h do |milestone| + [milestone.id, milestone] + end + end + + def relevant_parents + @relevant_parents ||= event_milestones.map { |_id, milestone| milestone.parent } + end + + def per_page + [params[:per_page], MAX_PER_PAGE].compact.min + end + + def page + params[:page] || 1 + end +end diff --git a/app/models/ci/instance_variable.rb b/app/models/ci/instance_variable.rb index 557f3a63280..89ace3f7ede 100644 --- a/app/models/ci/instance_variable.rb +++ b/app/models/ci/instance_variable.rb @@ -13,6 +13,11 @@ module Ci message: "(%{value}) has already been taken" } + validates :encrypted_value, length: { + maximum: 1024, + too_long: 'The encrypted value of the provided variable exceeds %{count} bytes. Variables over 700 characters risk exceeding the limit.' + } + scope :unprotected, -> { where(protected: false) } after_commit { self.class.invalidate_memory_cache(:ci_instance_variable_data) } diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2b072b22454..f37525e56e4 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -401,7 +401,7 @@ module Ci # The `Ci::Stage` contains all up-to date data # as atomic processing updates all data in-bulk stages - elsif Feature.enabled?(:ci_pipeline_persisted_stages, default_enabled: true) && complete? + elsif complete? # The `Ci::Stage` contains up-to date data only for `completed` pipelines # this is due to asynchronous processing of pipeline, and stages possibly # not updated inline with processing of pipeline diff --git a/app/models/event.rb b/app/models/event.rb index 12b85697690..25d016291a7 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -21,6 +21,7 @@ class Event < ApplicationRecord LEFT = 9 # User left project DESTROYED = 10 EXPIRED = 11 # User left project due to expiry + APPROVED = 12 ACTIONS = HashWithIndifferentAccess.new( created: CREATED, @@ -33,7 +34,8 @@ class Event < ApplicationRecord joined: JOINED, left: LEFT, destroyed: DESTROYED, - expired: EXPIRED + expired: EXPIRED, + approved: APPROVED ).freeze WIKI_ACTIONS = [CREATED, UPDATED, DESTROYED].freeze diff --git a/app/models/resource_milestone_event.rb b/app/models/resource_milestone_event.rb index 039f26d8e3f..36068cf508b 100644 --- a/app/models/resource_milestone_event.rb +++ b/app/models/resource_milestone_event.rb @@ -9,6 +9,8 @@ class ResourceMilestoneEvent < ResourceEvent validate :exactly_one_issuable + scope :include_relations, -> { includes(:user, milestone: [:project, :group]) } + enum action: { add: 1, remove: 2 @@ -26,4 +28,12 @@ class ResourceMilestoneEvent < ResourceEvent def milestone_title milestone&.title end + + def milestone_parent + milestone&.parent + end + + def issuable + issue || merge_request + end end diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb index 39a6889fc84..abac0ffc5d9 100644 --- a/app/services/groups/import_export/export_service.rb +++ b/app/services/groups/import_export/export_service.rb @@ -22,7 +22,7 @@ module Groups save! ensure - cleanup + remove_base_tmp_dir end private @@ -81,8 +81,8 @@ module Groups Gitlab::ImportExport::Saver.new(exportable: @group, shared: @shared) end - def cleanup - FileUtils.rm_rf(shared.archive_path) if shared&.archive_path + def remove_base_tmp_dir + FileUtils.rm_rf(shared.base_path) if shared&.base_path end def notify_error! diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb index dcd78210801..bd611d55847 100644 --- a/app/services/groups/import_export/import_service.rb +++ b/app/services/groups/import_export/import_service.rb @@ -26,6 +26,7 @@ module Groups end ensure + remove_base_tmp_dir remove_import_file end @@ -102,6 +103,10 @@ module Groups raise Gitlab::ImportExport::Error.new(@shared.errors.to_sentence) end + + def remove_base_tmp_dir + FileUtils.rm_rf(@shared.base_path) + end end end end |