diff options
Diffstat (limited to 'app')
36 files changed, 266 insertions, 46 deletions
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index f65e22a31c5..f56c402091f 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -7,6 +7,7 @@ import csrf from './lib/utils/csrf'; import axios from './lib/utils/axios_utils'; import { n__, __ } from '~/locale'; import { getFilename } from '~/lib/utils/file_upload'; +import { spriteIcon } from '~/lib/utils/common_utils'; Dropzone.autoDiscover = false; @@ -25,7 +26,7 @@ function getErrorMessage(res) { export default function dropzoneInput(form, config = { parallelUploads: 2 }) { const divHover = '<div class="div-dropzone-hover"></div>'; - const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; + const iconPaperclip = spriteIcon('paperclip', 'div-dropzone-icon'); const $attachButton = form.find('.button-attach-file'); const $attachingFileMessage = form.find('.attaching-file-message'); const $cancelButton = form.find('.button-cancel-uploading-files'); diff --git a/app/assets/javascripts/issuable_list/components/issuable_item.vue b/app/assets/javascripts/issuable_list/components/issuable_item.vue index d8cb1ab07cd..283e49c287a 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_item.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_item.vue @@ -1,15 +1,20 @@ <script> -import { GlLink, GlLabel, GlTooltipDirective } from '@gitlab/ui'; +import { GlLink, GlIcon, GlLabel, GlTooltipDirective } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getTimeago } from '~/lib/utils/datetime_utility'; import { isScopedLabel } from '~/lib/utils/common_utils'; import timeagoMixin from '~/vue_shared/mixins/timeago'; +import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; + export default { components: { GlLink, + GlIcon, GlLabel, + IssuableAssignees, }, directives: { GlTooltip: GlTooltipDirective, @@ -24,25 +29,33 @@ export default { type: Object, required: true, }, + enableLabelPermalinks: { + type: Boolean, + required: true, + }, }, computed: { author() { return this.issuable.author; }, authorId() { - const id = parseInt(this.author.id, 10); - - if (Number.isNaN(id)) { - return this.author.id.includes('gid') - ? this.author.id.split('gid://gitlab/User/').pop() - : ''; + return getIdFromGraphQLId(`${this.author.id}`); + }, + isIssuableUrlExternal() { + // Check if URL is relative, which means it is internal. + if (!/^https?:\/\//g.test(this.issuable.webUrl)) { + return false; } - - return id; + // In case URL is absolute, it may or may not be internal, + // hence use `gon.gitlab_url` which is current instance domain. + return !this.issuable.webUrl.includes(gon.gitlab_url); }, labels() { return this.issuable.labels?.nodes || this.issuable.labels || []; }, + assignees() { + return this.issuable.assignees || []; + }, createdAt() { return sprintf(__('created %{timeAgo}'), { timeAgo: getTimeago().format(this.issuable.createdAt), @@ -53,11 +66,33 @@ export default { timeAgo: getTimeago().format(this.issuable.updatedAt), }); }, + issuableTitleProps() { + if (this.isIssuableUrlExternal) { + return { + target: '_blank', + }; + } + return {}; + }, }, methods: { + hasSlotContents(slotName) { + return Boolean(this.$slots[slotName]); + }, scopedLabel(label) { return isScopedLabel(label); }, + labelTitle(label) { + return label.title || label.name; + }, + labelTarget(label) { + if (this.enableLabelPermalinks) { + const key = encodeURIComponent('label_name[]'); + const value = encodeURIComponent(this.labelTitle(label)); + return `?${key}=${value}`; + } + return '#'; + }, /** * This is needed as an independent method since * when user changes current page, `$refs.authorLink` @@ -74,17 +109,20 @@ export default { </script> <template> - <li class="issue"> + <li class="issue px-3"> <div class="issue-box"> <div class="issuable-info-container"> <div class="issuable-main-info"> <div data-testid="issuable-title" class="issue-title title"> <span class="issue-title-text" dir="auto"> - <gl-link :href="issuable.webUrl">{{ issuable.title }}</gl-link> + <gl-link :href="issuable.webUrl" v-bind="issuableTitleProps" + >{{ issuable.title }}<gl-icon v-if="isIssuableUrlExternal" name="external-link" + /></gl-link> </span> </div> <div class="issuable-info"> - <span data-testid="issuable-reference" class="issuable-reference" + <slot v-if="hasSlotContents('reference')" name="reference"></slot> + <span v-else data-testid="issuable-reference" class="issuable-reference" >{{ issuableSymbol }}{{ issuable.iid }}</span > <span class="issuable-authored d-none d-sm-inline-block"> @@ -113,15 +151,30 @@ export default { v-for="(label, index) in labels" :key="index" :background-color="label.color" - :title="label.title" + :title="labelTitle(label)" :description="label.description" :scoped="scopedLabel(label)" + :target="labelTarget(label)" :class="{ 'gl-ml-2': index }" size="sm" /> </div> </div> <div class="issuable-meta"> + <ul v-if="hasSlotContents('status') || issuable.assignees" class="controls"> + <li v-if="hasSlotContents('status')" class="issuable-status"> + <slot name="status"></slot> + </li> + <li v-if="assignees.length" class="gl-display-flex"> + <issuable-assignees + :assignees="issuable.assignees" + :icon-size="16" + :max-visible="4" + img-css-classes="gl-mr-2!" + class="gl-align-items-center gl-display-flex gl-ml-3" + /> + </li> + </ul> <div data-testid="issuable-updated-at" class="float-right issuable-updated-at d-none d-sm-inline-block" diff --git a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue index 7535203dea1..52ebb9acdc3 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue @@ -1,6 +1,7 @@ <script> import { GlLoadingIcon, GlPagination } from '@gitlab/ui'; +import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import IssuableTabs from './issuable_tabs.vue'; @@ -35,6 +36,11 @@ export default { type: Array, required: true, }, + urlParams: { + type: Object, + required: false, + default: () => ({}), + }, initialFilterValue: { type: Array, required: false, @@ -55,7 +61,8 @@ export default { }, tabCounts: { type: Object, - required: true, + required: false, + default: null, }, currentTab: { type: String, @@ -81,6 +88,11 @@ export default { required: false, default: 20, }, + totalPages: { + type: Number, + required: false, + default: 0, + }, currentPage: { type: Number, required: false, @@ -96,6 +108,26 @@ export default { required: false, default: 2, }, + enableLabelPermalinks: { + type: Boolean, + required: false, + default: true, + }, + }, + watch: { + urlParams: { + deep: true, + immediate: true, + handler(params) { + if (Object.keys(params).length) { + updateHistory({ + url: setUrlParams(params, window.location.href, true), + title: document.title, + replace: true, + }); + } + }, + }, }, }; </script> @@ -135,12 +167,21 @@ export default { :key="issuable.id" :issuable-symbol="issuableSymbol" :issuable="issuable" - /> + :enable-label-permalinks="enableLabelPermalinks" + > + <template #reference> + <slot name="reference" :issuable="issuable"></slot> + </template> + <template #status> + <slot name="status" :issuable="issuable"></slot> + </template> + </issuable-item> </ul> <slot v-if="!issuablesLoading && !issuables.length" name="empty-state"></slot> <gl-pagination v-if="showPaginationControls" :per-page="defaultPageSize" + :total-items="totalPages" :value="currentPage" :prev-page="previousPage" :next-page="nextPage" diff --git a/app/assets/javascripts/issuable_list/components/issuable_tabs.vue b/app/assets/javascripts/issuable_list/components/issuable_tabs.vue index df544ce69e7..d9aab004077 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_tabs.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_tabs.vue @@ -14,7 +14,8 @@ export default { }, tabCounts: { type: Object, - required: true, + required: false, + default: null, }, currentTab: { type: String, @@ -40,7 +41,7 @@ export default { > <template #title> <span :title="tab.titleTooltip">{{ tab.title }}</span> - <gl-badge variant="neutral" size="sm" class="gl-px-2 gl-py-1!">{{ + <gl-badge v-if="tabCounts" variant="neutral" size="sm" class="gl-px-2 gl-py-1!">{{ tabCounts[tab.name] }}</gl-badge> </template> diff --git a/app/assets/javascripts/issuable_list/constants.js b/app/assets/javascripts/issuable_list/constants.js new file mode 100644 index 00000000000..4d3ce61366a --- /dev/null +++ b/app/assets/javascripts/issuable_list/constants.js @@ -0,0 +1,49 @@ +import { __ } from '~/locale'; + +export const IssuableStates = { + Opened: 'opened', + Closed: 'closed', + All: 'all', +}; + +export const IssuableListTabs = [ + { + id: 'state-opened', + name: IssuableStates.Opened, + title: __('Open'), + titleTooltip: __('Filter by issues that are currently opened.'), + }, + { + id: 'state-closed', + name: IssuableStates.Closed, + title: __('Closed'), + titleTooltip: __('Filter by issues that are currently closed.'), + }, + { + id: 'state-all', + name: IssuableStates.All, + title: __('All'), + titleTooltip: __('Show all issues.'), + }, +]; + +export const AvailableSortOptions = [ + { + id: 1, + title: __('Created date'), + sortDirection: { + descending: 'created_desc', + ascending: 'created_asc', + }, + }, + { + id: 2, + title: __('Last updated'), + sortDirection: { + descending: 'updated_desc', + ascending: 'updated_asc', + }, + }, +]; + +export const DEFAULT_PAGE_SIZE = 20; diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue index 72f9a4c7e74..b20facc4032 100644 --- a/app/assets/javascripts/notes/components/note_attachment.vue +++ b/app/assets/javascripts/notes/components/note_attachment.vue @@ -1,6 +1,11 @@ <script> +import { GlIcon } from '@gitlab/ui'; + export default { name: 'NoteAttachment', + components: { + GlIcon, + }, props: { attachment: { type: Object, @@ -29,7 +34,7 @@ export default { target="_blank" rel="noopener noreferrer" > - <i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }} + <gl-icon name="paperclip" /> {{ attachment.filename }} </a> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue index 97595e5d2ce..e52afe08336 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue @@ -87,7 +87,7 @@ export default { :aria-label="__('Run manual or delayed jobs')" > <gl-icon name="play" class="icon-play" /> - <i class="fa fa-caret-down" aria-hidden="true"></i> + <gl-icon name="chevron-down" /> <gl-loading-icon v-if="isLoading" /> </button> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue index 4a3d134685e..55c71e299be 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue @@ -29,7 +29,7 @@ export default { :aria-label="__('Artifacts')" > <gl-icon name="download" /> - <i class="fa fa-caret-down" aria-hidden="true"></i> + <gl-icon name="chevron-down" /> </button> <ul class="dropdown-menu dropdown-menu-right"> <li v-for="(artifact, i) in artifacts" :key="i"> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue index 9d72bf4394e..7b67c34ded6 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue @@ -96,7 +96,12 @@ export default { </script> <template> - <div v-gl-tooltip:body.viewport.left :title="tooltipText" class="sidebar-collapsed-icon"> + <div + v-gl-tooltip:body.viewport.left + :title="tooltipText" + data-testid="collapsedState" + class="sidebar-collapsed-icon" + > <gl-icon name="timer" /> <div class="time-tracking-collapsed-summary"> <div :class="divClass"> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue index d4cc98e3743..99302993b9a 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue @@ -70,14 +70,19 @@ export default { </script> <template> - <div class="time-tracking-comparison-pane"> + <div data-testid="timeTrackingComparisonPane"> <div v-gl-tooltip + data-testid="compareMeter" :title="timeRemainingTooltip" :class="timeRemainingStatusClass" class="compare-meter" > - <gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" /> + <gl-progress-bar + data-testid="timeRemainingProgress" + :value="timeRemainingPercent" + :variant="progressBarVariant" + /> <div class="compare-display-container"> <div class="compare-display float-left"> <span class="compare-label">{{ s__('TimeTracking|Spent') }}</span> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue index 305726d9725..8a80b1bf13f 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue @@ -11,7 +11,8 @@ export default { </script> <template> - <div class="time-tracking-estimate-only-pane"> - <span class="bold"> {{ s__('TimeTracking|Estimated:') }} </span> {{ timeEstimateHumanReadable }} + <div data-testid="estimateOnlyPane"> + <span class="gl-font-weight-bold">{{ s__('TimeTracking|Estimated:') }} </span + >{{ timeEstimateHumanReadable }} </div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue index b45746e789d..8bc828091c0 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue @@ -34,7 +34,7 @@ export default { </script> <template> - <div class="time-tracking-help-state"> + <div data-testid="helpPane" class="time-tracking-help-state"> <div class="time-tracking-info"> <h4>{{ __('Track time with quick actions') }}</h4> <p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue index 45552589e50..2d3d0ce8dc5 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue @@ -5,7 +5,7 @@ export default { </script> <template> - <div class="time-tracking-no-tracking-pane"> - <span class="no-value"> {{ __('No estimate or time spent') }} </span> + <div data-testid="noTrackingPane"> + <span class="no-value">{{ __('No estimate or time spent') }}</span> </div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue index b2b3b289c5c..33c6ac6e2ba 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue @@ -15,7 +15,7 @@ export default { return sprintf( s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'), { - startTag: '<span class="bold">', + startTag: '<span class="gl-font-weight-bold">', endTag: '</span>', timeSpentHumanReadable: this.timeSpentHumanReadable, }, @@ -27,5 +27,5 @@ export default { </script> <template> - <div class="time-tracking-spend-only-pane" v-html="timeSpent"></div> + <div data-testid="spentOnlyPane" v-html="timeSpent"></div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index a2fb0ebcbc6..95864462a68 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -105,11 +105,17 @@ export default { /> <div class="title hide-collapsed"> {{ __('Time tracking') }} - <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)"> + <div + v-if="!showHelpState" + data-testid="helpButton" + class="help-button float-right" + @click="toggleHelpState(true)" + > <gl-icon name="question-o" /> </div> <div - v-if="showHelpState" + v-else + data-testid="closeHelpButton" class="close-help-button float-right" @click="toggleHelpState(false)" > diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js index c1521882682..0a1211d0a76 100644 --- a/app/assets/javascripts/tracking.js +++ b/app/assets/javascripts/tracking.js @@ -12,6 +12,7 @@ const DEFAULT_SNOWPLOW_OPTIONS = { contexts: { webPage: true, performanceTiming: true }, formTracking: false, linkClickTracking: false, + pageUnloadTimer: 10, }; const createEventPayload = (el, { suffix = '' } = {}) => { diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index a31cb0b0485..ad4d42abae6 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -39,7 +39,6 @@ @import './pages/settings'; @import './pages/settings_ci_cd'; @import './pages/sherlock'; -@import './pages/status'; @import './pages/storage_quota'; @import './pages/tree'; @import './pages/trials'; diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss index 7b1953be69d..937ed5119cb 100644 --- a/app/assets/stylesheets/fontawesome_custom.scss +++ b/app/assets/stylesheets/fontawesome_custom.scss @@ -117,10 +117,6 @@ content: '\f077'; } -.fa-paperclip::before { - content: '\f0c6'; -} - .fa-bug::before { content: '\f188'; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/page_bundles/ci_status.scss index b37c5172ad2..8522a0a8fe4 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/page_bundles/ci_status.scss @@ -1,3 +1,5 @@ +@import 'mixins_and_variables_and_functions'; + .ci-status { padding: 2px 7px 4px; border: 1px solid $gray-darker; diff --git a/app/assets/stylesheets/page_bundles/todos.scss b/app/assets/stylesheets/page_bundles/todos.scss index 3eec5b53a30..3e20ca9c62f 100644 --- a/app/assets/stylesheets/page_bundles/todos.scss +++ b/app/assets/stylesheets/page_bundles/todos.scss @@ -219,7 +219,6 @@ .todos-empty-content { align-self: center; max-width: 480px; - margin-right: 20px; } .todos-empty-hero { diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb index 85892c2abeb..8e8127cf279 100644 --- a/app/graphql/resolvers/releases_resolver.rb +++ b/app/graphql/resolvers/releases_resolver.rb @@ -4,6 +4,10 @@ module Resolvers class ReleasesResolver < BaseResolver type Types::ReleaseType.connection_type, null: true + argument :sort, Types::ReleaseSortEnum, + required: false, default_value: :released_at_desc, + description: 'Sort releases by this criteria' + alias_method :project, :object # This resolver has a custom singular resolver @@ -11,12 +15,20 @@ module Resolvers Resolvers::ReleaseResolver end - def resolve(**args) + SORT_TO_PARAMS_MAP = { + released_at_desc: { order_by: 'released_at', sort: 'desc' }, + released_at_asc: { order_by: 'released_at', sort: 'asc' }, + created_desc: { order_by: 'created_at', sort: 'desc' }, + created_asc: { order_by: 'created_at', sort: 'asc' } + }.freeze + + def resolve(sort:) return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true) ReleasesFinder.new( project, - current_user + current_user, + SORT_TO_PARAMS_MAP[sort] ).execute end end diff --git a/app/graphql/types/release_sort_enum.rb b/app/graphql/types/release_sort_enum.rb new file mode 100644 index 00000000000..2f9af1bced9 --- /dev/null +++ b/app/graphql/types/release_sort_enum.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Types + # Not inheriting from Types::SortEnum since we only want + # to implement a subset of the sort values it defines. + class ReleaseSortEnum < BaseEnum + graphql_name 'ReleaseSort' + description 'Values for sorting releases' + + # Borrowed from Types::SortEnum + # These values/descriptions should stay in-sync as much as possible. + value 'CREATED_DESC', 'Created at descending order', value: :created_desc + value 'CREATED_ASC', 'Created at ascending order', value: :created_asc + + value 'RELEASED_AT_DESC', 'Released at by descending order', value: :released_at_desc + value 'RELEASED_AT_ASC', 'Released at by ascending order', value: :released_at_asc + end +end diff --git a/app/models/service.rb b/app/models/service.rb index 764f417362f..0998a9a102a 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -8,6 +8,7 @@ class Service < ApplicationRecord include ProjectServicesLoggable include DataFields include FromUnion + include EachBatch SERVICE_NAMES = %w[ alerts asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker discord @@ -294,7 +295,7 @@ class Service < ApplicationRecord end def initialize_properties - self.properties = {} if properties.nil? + self.properties = {} if has_attribute?(:properties) && properties.nil? end def title diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb index 96a6d861e47..b5f034186df 100644 --- a/app/services/admin/propagate_integration_service.rb +++ b/app/services/admin/propagate_integration_service.rb @@ -20,7 +20,7 @@ module Admin # rubocop: disable Cop/InBatches def update_inherited_integrations - Service.by_type(integration.type).inherit_from_id(integration.id).in_batches(of: BATCH_SIZE) do |services| + Service.by_type(integration.type).inherit_from_id(integration.id).each_batch(of: BATCH_SIZE) do |services| min_id, max_id = services.pick("MIN(services.id), MAX(services.id)") PropagateIntegrationInheritWorker.perform_async(integration.id, min_id, max_id) end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 48f44affb23..b2826b5c905 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -73,6 +73,8 @@ module Notes if note.for_merge_request? && note.diff_note? && note.start_of_discussion? Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion) end + + track_note_creation_usage_for_issues(note) if note.for_issue? end def do_commands(note, update_params, message, only_commands) @@ -113,5 +115,9 @@ module Notes track_usage_event(:incident_management_incident_comment, user.id) end + + def track_note_creation_usage_for_issues(note) + Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_added_action(author: note.author) + end end end diff --git a/app/services/notes/destroy_service.rb b/app/services/notes/destroy_service.rb index ee8a680fcb4..2b6ec47eaef 100644 --- a/app/services/notes/destroy_service.rb +++ b/app/services/notes/destroy_service.rb @@ -8,6 +8,13 @@ module Notes end clear_noteable_diffs_cache(note) + track_note_removal_usage_for_issues(note) if note.for_issue? + end + + private + + def track_note_removal_usage_for_issues(note) + Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_removed_action(author: note.author) end end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 193d3080078..37872f7fbdb 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -14,6 +14,8 @@ module Notes note.save end + track_note_edit_usage_for_issues(note) if note.for_issue? + only_commands = false quick_actions_service = QuickActionsService.new(project, current_user) @@ -89,6 +91,10 @@ module Notes Note.id_in(note.discussion.notes.map(&:id)).update_all(confidential: params[:confidential]) end + + def track_note_edit_usage_for_issues(note) + Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_comment_edited_action(author: note.author) + end end end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index ebff5a182c4..9a9fbfc1ee8 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -114,7 +114,7 @@ .todos-empty .todos-empty-hero.svg-content = image_tag 'illustrations/todos_empty.svg' - .todos-empty-content + .todos-empty-content.gl-mx-5 %h4 Your To-Do List shows what to work on next %p diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 4e936025c74..2fa595503e5 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -25,5 +25,5 @@ = image_tag note.attachment.url, class: 'note-image-attach' - else = link_to note.attachment.url, target: '_blank', class: 'note-file-attach' do - %i.fa.fa-paperclip + = sprite_icon("paperclip") = note.attachment_identifier diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index 0b4b4aafeee..a1960fc99cf 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -1,4 +1,5 @@ - page_title _("Jobs") +- add_page_specific_style 'page_bundles/ci_status' .top-area - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) } diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index d7a778088ee..3ac81d4fad6 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -2,6 +2,7 @@ - breadcrumb_title "##{@build.id}" - page_title "#{@build.name} (##{@build.id})", _("Jobs") - add_page_specific_style 'page_bundles/xterm' +- add_page_specific_style 'page_bundles/ci_status' = render_if_exists "shared/shared_runners_minutes_limit_flash_message" diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml index 4c968c8e8eb..0741b24a5a1 100644 --- a/app/views/projects/merge_requests/creations/new.html.haml +++ b/app/views/projects/merge_requests/creations/new.html.haml @@ -2,6 +2,7 @@ - breadcrumb_title _("New") - page_title _("New Merge Request") - add_page_specific_style 'page_bundles/pipelines' +- add_page_specific_style 'page_bundles/ci_status' - if @merge_request.can_be_created && !params[:change_branches] = render 'new_submit' diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 1dbcd613ceb..6b506c38795 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -11,6 +11,7 @@ - add_page_specific_style 'page_bundles/merge_requests' - add_page_specific_style 'page_bundles/pipelines' - add_page_specific_style 'page_bundles/reports' +- add_page_specific_style 'page_bundles/ci_status' .merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } } = render "projects/merge_requests/mr_title" diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index ca07f33136b..6aa1a564499 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -1,5 +1,6 @@ - page_title _('Pipelines') - add_page_specific_style 'page_bundles/pipelines' +- add_page_specific_style 'page_bundles/ci_status' = render_if_exists "shared/shared_runners_minutes_limit_flash_message" diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 34f7744f825..a25c30d01d9 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -4,6 +4,7 @@ - pipeline_has_errors = @pipeline.builds.empty? && @pipeline.yaml_errors.present? - add_page_specific_style 'page_bundles/pipeline' - add_page_specific_style 'page_bundles/reports' +- add_page_specific_style 'page_bundles/ci_status' .js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } } #js-pipeline-header-vue.pipeline-header-container{ data: {full_path: @project.full_path, retry_path: retry_project_pipeline_path(@pipeline.project, @pipeline), cancel_path: cancel_project_pipeline_path(@pipeline.project, @pipeline), delete_path: project_pipeline_path(@pipeline.project, @pipeline), pipeline_iid: @pipeline.iid, pipeline_id: @pipeline.id} } diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 97ed2852871..f1352be28e3 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -72,7 +72,7 @@ = image_tag note.attachment.url, class: 'note-image-attach' .attachment = link_to note.attachment.url, target: '_blank' do - = icon('paperclip') + = sprite_icon('paperclip') = note.attachment_identifier = link_to delete_attachment_project_note_path(note.project, note), title: _('Delete this attachment'), method: :delete, remote: true, data: { confirm: _('Are you sure you want to remove the attachment?') }, class: 'danger js-note-attachment-delete' do |