diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-07 15:10:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-07 15:10:50 +0000 |
commit | 807c4eae46f96ccd54ce1d8d13f4547eda017267 (patch) | |
tree | 190aaf8d8c0a766fa7fc396355fd5e0d865db889 /app | |
parent | ebe0e306bbd6e913763bf1865b7778c001994e31 (diff) | |
download | gitlab-ce-807c4eae46f96ccd54ce1d8d13f4547eda017267.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
27 files changed, 399 insertions, 138 deletions
diff --git a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue index a7331cc1fd3..1b7782c6860 100644 --- a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue +++ b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue @@ -68,9 +68,8 @@ export default { variables() { return this.queryVariables; }, - update({ project: { jobs: { nodes = [], pageInfo = {}, count = 0 } = {} } }) { + update({ project: { jobs: { nodes = [], pageInfo = {} } = {} } }) { this.pageInfo = pageInfo; - this.count = count; return nodes .map(mapArchivesToJobNodes) .map(mapBooleansToJobNodes) @@ -93,7 +92,6 @@ export default { data() { return { jobArtifacts: [], - count: 0, pageInfo: {}, expandedJobs: [], pagination: INITIAL_PAGINATION_STATE, @@ -110,7 +108,9 @@ export default { }; }, showPagination() { - return this.count > JOBS_PER_PAGE; + const { hasNextPage, hasPreviousPage } = this.pageInfo; + + return hasNextPage || hasPreviousPage; }, prevPage() { return Number(this.pageInfo.hasPreviousPage); diff --git a/app/assets/javascripts/work_items/components/notes/activity_filter.vue b/app/assets/javascripts/work_items/components/notes/activity_filter.vue index 71784d3a807..6d5535797ef 100644 --- a/app/assets/javascripts/work_items/components/notes/activity_filter.vue +++ b/app/assets/javascripts/work_items/components/notes/activity_filter.vue @@ -1,18 +1,35 @@ <script> import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { s__ } from '~/locale'; import Tracking from '~/tracking'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; -import { ASC, DESC } from '~/notes/constants'; -import { TRACKING_CATEGORY_SHOW, WORK_ITEM_NOTES_SORT_ORDER_KEY } from '~/work_items/constants'; +import { + WORK_ITEM_NOTES_FILTER_ALL_NOTES, + WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS, + WORK_ITEM_NOTES_FILTER_ONLY_HISTORY, + TRACKING_CATEGORY_SHOW, + WORK_ITEM_NOTES_FILTER_KEY, +} from '~/work_items/constants'; -const SORT_OPTIONS = [ - { key: DESC, text: __('Newest first'), dataid: 'js-newest-first' }, - { key: ASC, text: __('Oldest first'), dataid: 'js-oldest-first' }, +const filterOptions = [ + { + key: WORK_ITEM_NOTES_FILTER_ALL_NOTES, + text: s__('WorkItem|All activity'), + }, + { + key: WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS, + text: s__('WorkItem|Comments only'), + testid: 'comments-activity', + }, + { + key: WORK_ITEM_NOTES_FILTER_ONLY_HISTORY, + text: s__('WorkItem|History only'), + testid: 'history-activity', + }, ]; export default { - SORT_OPTIONS, + filterOptions, components: { GlDropdown, GlDropdownItem, @@ -20,11 +37,6 @@ export default { }, mixins: [Tracking.mixin()], props: { - sortOrder: { - type: String, - default: ASC, - required: false, - }, loading: { type: Boolean, default: false, @@ -34,80 +46,74 @@ export default { type: String, required: true, }, - }, - data() { - return { - persistSortOrder: true, - }; + discussionFilter: { + type: String, + default: WORK_ITEM_NOTES_FILTER_ALL_NOTES, + required: false, + }, }, computed: { tracking() { return { category: TRACKING_CATEGORY_SHOW, - label: 'item_track_notes_sorting', + label: 'item_track_notes_filtering', property: `type_${this.workItemType}`, }; }, - selectedSortOption() { - const isSortOptionValid = this.sortOrder === ASC || this.sortOrder === DESC; - return isSortOptionValid ? SORT_OPTIONS.find(({ key }) => this.sortOrder === key) : ASC; - }, getDropdownSelectedText() { return this.selectedSortOption.text; }, + selectedSortOption() { + return ( + filterOptions.find(({ key }) => this.discussionFilter === key) || + WORK_ITEM_NOTES_FILTER_ALL_NOTES + ); + }, }, methods: { - setDiscussionSortDirection(direction) { - this.$emit('updateSavedSortOrder', direction); + setDiscussionFilterOption(filterValue) { + this.$emit('changeFilter', filterValue); }, - fetchSortedDiscussions(direction) { - if (this.isSortDropdownItemActive(direction)) { + fetchFilteredDiscussions(filterValue) { + if (this.isSortDropdownItemActive(filterValue)) { return; } - this.track('notes_sort_order_changed'); - this.$emit('changeSortOrder', direction); + this.track('work_item_notes_filter_changed'); + this.$emit('changeFilter', filterValue); }, - isSortDropdownItemActive(sortDir) { - return sortDir === this.sortOrder; + isSortDropdownItemActive(discussionFilter) { + return discussionFilter === this.discussionFilter; }, }, - WORK_ITEM_NOTES_SORT_ORDER_KEY, + WORK_ITEM_NOTES_FILTER_KEY, }; </script> <template> - <div - id="discussion-preferences" - data-testid="discussion-preferences" - class="gl-display-inline-block gl-vertical-align-bottom gl-w-full gl-sm-w-auto" - > + <div class="gl-display-inline-block gl-vertical-align-bottom"> <local-storage-sync - :value="sortOrder" - :storage-key="$options.WORK_ITEM_NOTES_SORT_ORDER_KEY" - :persist="persistSortOrder" + :value="discussionFilter" + :storage-key="$options.WORK_ITEM_NOTES_FILTER_KEY" as-string - @input="setDiscussionSortDirection" + @input="setDiscussionFilterOption" /> <gl-dropdown - :id="`discussion-preferences-dropdown-${workItemType}`" class="gl-xs-w-full" size="small" :text="getDropdownSelectedText" :disabled="loading" right > - <div id="discussion-sort"> - <gl-dropdown-item - v-for="{ text, key, dataid } in $options.SORT_OPTIONS" - :key="text" - :data-testid="dataid" - is-check-item - :is-checked="isSortDropdownItemActive(key)" - @click="fetchSortedDiscussions(key)" - > - {{ text }} - </gl-dropdown-item> - </div> + <gl-dropdown-item + v-for="{ text, key, testid } in $options.filterOptions" + :key="text" + :data-testid="testid" + is-check-item + :is-checked="isSortDropdownItemActive(key)" + @click="fetchFilteredDiscussions(key)" + > + {{ text }} + </gl-dropdown-item> </gl-dropdown> </div> </template> diff --git a/app/assets/javascripts/work_items/components/notes/activity_sort.vue b/app/assets/javascripts/work_items/components/notes/activity_sort.vue new file mode 100644 index 00000000000..bfbb2b65346 --- /dev/null +++ b/app/assets/javascripts/work_items/components/notes/activity_sort.vue @@ -0,0 +1,99 @@ +<script> +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { __ } from '~/locale'; +import Tracking from '~/tracking'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import { ASC, DESC } from '~/notes/constants'; +import { TRACKING_CATEGORY_SHOW, WORK_ITEM_NOTES_SORT_ORDER_KEY } from '~/work_items/constants'; + +const sortOptions = [ + { key: DESC, text: __('Newest first'), testid: 'newest-first' }, + { key: ASC, text: __('Oldest first') }, +]; + +export default { + sortOptions, + components: { + GlDropdown, + GlDropdownItem, + LocalStorageSync, + }, + mixins: [Tracking.mixin()], + props: { + sortOrder: { + type: String, + default: ASC, + required: false, + }, + loading: { + type: Boolean, + default: false, + required: false, + }, + workItemType: { + type: String, + required: true, + }, + }, + computed: { + tracking() { + return { + category: TRACKING_CATEGORY_SHOW, + label: 'item_track_notes_sorting', + property: `type_${this.workItemType}`, + }; + }, + selectedSortOption() { + return sortOptions.find(({ key }) => this.sortOrder === key) || ASC; + }, + getDropdownSelectedText() { + return this.selectedSortOption.text; + }, + }, + methods: { + setDiscussionSortDirection(direction) { + this.$emit('changeSort', direction); + }, + fetchSortedDiscussions(direction) { + if (this.isSortDropdownItemActive(direction)) { + return; + } + this.track('work_item_notes_sort_order_changed'); + this.$emit('changeSort', direction); + }, + isSortDropdownItemActive(sortDir) { + return sortDir === this.sortOrder; + }, + }, + WORK_ITEM_NOTES_SORT_ORDER_KEY, +}; +</script> + +<template> + <div class="gl-display-inline-block gl-vertical-align-bottom"> + <local-storage-sync + :value="sortOrder" + :storage-key="$options.WORK_ITEM_NOTES_SORT_ORDER_KEY" + as-string + @input="setDiscussionSortDirection" + /> + <gl-dropdown + class="gl-xs-w-full" + size="small" + :text="getDropdownSelectedText" + :disabled="loading" + right + > + <gl-dropdown-item + v-for="{ text, key, testid } in $options.sortOptions" + :key="text" + :data-testid="testid" + is-check-item + :is-checked="isSortDropdownItemActive(key)" + @click="fetchSortedDiscussions(key)" + > + {{ text }} + </gl-dropdown-item> + </gl-dropdown> + </div> +</template> diff --git a/app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue new file mode 100644 index 00000000000..07e25312f87 --- /dev/null +++ b/app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue @@ -0,0 +1,61 @@ +<script> +import { GlButton, GlIcon, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +import { + WORK_ITEM_NOTES_FILTER_ALL_NOTES, + WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS, +} from '~/work_items/constants'; + +export default { + WORK_ITEM_NOTES_FILTER_ALL_NOTES, + WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS, + i18n: { + information: s__( + "WorkItem|You're only seeing %{boldStart}other activity%{boldEnd} in the feed. To add a comment, switch to one of the following options.", + ), + }, + components: { + GlButton, + GlIcon, + GlSprintf, + }, + methods: { + selectFilter(value) { + this.$emit('changeFilter', value); + }, + }, +}; +</script> + +<template> + <li class="timeline-entry note note-wrapper discussion-filter-note"> + <div class="timeline-icon gl-display-none gl-lg-display-flex"> + <gl-icon name="comment" /> + </div> + <div class="timeline-content gl-pl-8"> + <gl-sprintf :message="$options.i18n.information"> + <template #bold="{ content }"> + <b>{{ content }}</b> + </template> + </gl-sprintf> + + <div class="discussion-filter-actions"> + <gl-button + class="gl-mr-2 gl-mt-3" + data-testid="show-all-activity" + @click="selectFilter($options.WORK_ITEM_NOTES_FILTER_ALL_NOTES)" + > + {{ __('Show all activity') }} + </gl-button> + <gl-button + class="gl-mt-3" + data-testid="show-comments-only" + @click="selectFilter($options.WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS)" + > + {{ __('Show comments only') }} + </gl-button> + </div> + </div> + </li> +</template> diff --git a/app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue b/app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue new file mode 100644 index 00000000000..e700d5353e2 --- /dev/null +++ b/app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue @@ -0,0 +1,67 @@ +<script> +import ActivitySort from '~/work_items/components/notes/activity_sort.vue'; +import ActivityFilter from '~/work_items/components/notes/activity_filter.vue'; +import { s__ } from '~/locale'; +import { ASC } from '~/notes/constants'; +import { WORK_ITEM_NOTES_FILTER_ALL_NOTES } from '~/work_items/constants'; + +export default { + i18n: { + activityLabel: s__('WorkItem|Activity'), + }, + components: { + ActivitySort, + ActivityFilter, + }, + props: { + disableActivityFilterSort: { + type: Boolean, + required: true, + }, + sortOrder: { + type: String, + default: ASC, + required: false, + }, + workItemType: { + type: String, + required: true, + }, + discussionFilter: { + type: String, + default: WORK_ITEM_NOTES_FILTER_ALL_NOTES, + required: false, + }, + }, + methods: { + changeNotesSortOrder(direction) { + this.$emit('changeSort', direction); + }, + filterDiscussions(filterValue) { + this.$emit('changeFilter', filterValue); + }, + }, +}; +</script> + +<template> + <div + class="gl-display-flex gl-justify-content-space-between gl-flex-wrap gl-pb-3 gl-align-items-center" + > + <h3 class="gl-font-base gl-m-0">{{ $options.i18n.activityLabel }}</h3> + <div class="gl-display-flex gl-gap-3"> + <activity-filter + :loading="disableActivityFilterSort" + :work-item-type="workItemType" + :discussion-filter="discussionFilter" + @changeFilter="filterDiscussions" + /> + <activity-sort + :loading="disableActivityFilterSort" + :sort-order="sortOrder" + :work-item-type="workItemType" + @changeSort="changeNotesSortOrder" + /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/work_items/components/work_item_notes.vue b/app/assets/javascripts/work_items/components/work_item_notes.vue index aa6dd9b5184..331d0b5e8d0 100644 --- a/app/assets/javascripts/work_items/components/work_item_notes.vue +++ b/app/assets/javascripts/work_items/components/work_item_notes.vue @@ -1,11 +1,17 @@ <script> import { GlSkeletonLoader, GlModal } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; -import { s__, __ } from '~/locale'; +import { __ } from '~/locale'; import { TYPENAME_DISCUSSION, TYPENAME_NOTE } from '~/graphql_shared/constants'; import SystemNote from '~/work_items/components/notes/system_note.vue'; -import ActivityFilter from '~/work_items/components/notes/activity_filter.vue'; -import { i18n, DEFAULT_PAGE_SIZE_NOTES } from '~/work_items/constants'; +import WorkItemNotesActivityHeader from '~/work_items/components/notes/work_item_notes_activity_header.vue'; +import { + i18n, + DEFAULT_PAGE_SIZE_NOTES, + WORK_ITEM_NOTES_FILTER_ALL_NOTES, + WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS, + WORK_ITEM_NOTES_FILTER_ONLY_HISTORY, +} from '~/work_items/constants'; import { ASC, DESC } from '~/notes/constants'; import { getWorkItemNotesQuery } from '~/work_items/utils'; import { @@ -13,6 +19,7 @@ import { updateCacheAfterDeletingNote, } from '~/work_items/graphql/cache_utils'; import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue'; +import WorkItemHistoryOnlyFilterNote from '~/work_items/components/notes/work_item_history_only_filter_note.vue'; import workItemNoteCreatedSubscription from '~/work_items/graphql/notes/work_item_note_created.subscription.graphql'; import workItemNoteUpdatedSubscription from '~/work_items/graphql/notes/work_item_note_updated.subscription.graphql'; import workItemNoteDeletedSubscription from '~/work_items/graphql/notes/work_item_note_deleted.subscription.graphql'; @@ -20,9 +27,6 @@ import deleteNoteMutation from '../graphql/notes/delete_work_item_notes.mutation import WorkItemAddNote from './notes/work_item_add_note.vue'; export default { - i18n: { - ACTIVITY_LABEL: s__('WorkItem|Activity'), - }, loader: { repeat: 10, width: 1000, @@ -31,10 +35,11 @@ export default { components: { GlSkeletonLoader, GlModal, - ActivityFilter, SystemNote, WorkItemAddNote, WorkItemDiscussion, + WorkItemNotesActivityHeader, + WorkItemHistoryOnlyFilterNote, }, props: { workItemId: { @@ -65,6 +70,7 @@ export default { perPage: DEFAULT_PAGE_SIZE_NOTES, sortOrder: ASC, noteToDelete: null, + discussionFilter: WORK_ITEM_NOTES_FILTER_ALL_NOTES, }; }, computed: { @@ -83,7 +89,7 @@ export default { showLoadingMoreSkeleton() { return this.isLoadingMore && !this.changeNotesSortOrderAfterLoading; }, - disableActivityFilter() { + disableActivityFilterSort() { return this.initialLoading || this.isLoadingMore; }, formAtTop() { @@ -102,10 +108,27 @@ export default { notesArray() { const notes = this.workItemNotes?.nodes || []; + const visibleNotes = notes.filter((note) => { + const isSystemNote = this.isSystemNote(note); + + if (this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS && isSystemNote) { + return false; + } + + if (this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_HISTORY && !isSystemNote) { + return false; + } + + return true; + }); + if (this.sortOrder === DESC) { - return [...notes].reverse(); + return [...visibleNotes].reverse(); } - return notes; + return visibleNotes; + }, + commentsDisabled() { + return this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_HISTORY; }, }, apollo: { @@ -210,6 +233,9 @@ export default { changeNotesSortOrder(direction) { this.sortOrder = direction; }, + filterDiscussions(filterValue) { + this.discussionFilter = filterValue; + }, async fetchMoreNotes() { this.isLoadingMore = true; // copied from discussions batch logic - every fetchMore call has a higher @@ -271,17 +297,14 @@ export default { <template> <div class="gl-border-t gl-mt-5 work-item-notes"> - <div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap"> - <label class="gl-mb-0">{{ $options.i18n.ACTIVITY_LABEL }}</label> - <activity-filter - class="gl-min-h-5 gl-pb-3" - :loading="disableActivityFilter" - :sort-order="sortOrder" - :work-item-type="workItemType" - @changeSortOrder="changeNotesSortOrder" - @updateSavedSortOrder="changeNotesSortOrder" - /> - </div> + <work-item-notes-activity-header + :sort-order="sortOrder" + :disable-activity-filter-sort="disableActivityFilterSort" + :work-item-type="workItemType" + :discussion-filter="discussionFilter" + @changeSort="changeNotesSortOrder" + @changeFilter="filterDiscussions" + /> <div v-if="initialLoading" class="gl-mt-5"> <gl-skeleton-loader v-for="index in $options.loader.repeat" @@ -298,7 +321,7 @@ export default { <template v-if="!initialLoading"> <ul class="notes main-notes-list timeline gl-clearfix!"> <work-item-add-note - v-if="formAtTop" + v-if="formAtTop && !commentsDisabled" v-bind="workItemCommentFormProps" @error="$emit('error', $event)" /> @@ -325,10 +348,14 @@ export default { </template> <work-item-add-note - v-if="!formAtTop" + v-if="!formAtTop && !commentsDisabled" v-bind="workItemCommentFormProps" @error="$emit('error', $event)" /> + <work-item-history-only-filter-note + v-if="commentsDisabled" + @changeFilter="filterDiscussions" + /> </ul> </template> diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 81f9bf04bc8..b372f2d6f7b 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -176,3 +176,9 @@ export const DEFAULT_PAGE_SIZE_ASSIGNEES = 10; export const DEFAULT_PAGE_SIZE_NOTES = 30; export const WORK_ITEM_NOTES_SORT_ORDER_KEY = 'sort_direction_work_item'; + +export const WORK_ITEM_NOTES_FILTER_ALL_NOTES = 'ALL_NOTES'; +export const WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS = 'ONLY_COMMENTS'; +export const WORK_ITEM_NOTES_FILTER_ONLY_HISTORY = 'ONLY_HISTORY'; + +export const WORK_ITEM_NOTES_FILTER_KEY = 'filter_key_work_item'; diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb index 1b3d9223502..18c6f0bb9d3 100644 --- a/app/controllers/ide_controller.rb +++ b/app/controllers/ide_controller.rb @@ -10,7 +10,6 @@ class IdeController < ApplicationController before_action do push_frontend_feature_flag(:build_service_proxy) push_frontend_feature_flag(:reject_unsigned_commits_by_gitlab) - define_index_vars end feature_category :web_ide @@ -22,6 +21,7 @@ class IdeController < ApplicationController if project Gitlab::Tracking.event(self.class.to_s, 'web_ide_views', namespace: project.namespace, user: current_user) + @fork_info = fork_info(project, params[:branch]) end render layout: 'fullscreen', locals: { minimal: helpers.use_new_web_ide? } @@ -33,16 +33,6 @@ class IdeController < ApplicationController render_404 unless can?(current_user, :read_project, project) end - def define_index_vars - return unless project - - @branch = params[:branch] - @path = params[:path] - @merge_request = params[:merge_request_id] - @learn_gitlab_source = params[:learn_gitlab_source] - @fork_info = fork_info(project, @branch) - end - def fork_info(project, branch) return if can?(current_user, :push_code, project) diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 296fe6856ac..063eef41f77 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -1,21 +1,26 @@ # frozen_string_literal: true module IdeHelper - def ide_data(project:, branch:, path:, merge_request:, fork_info:, learn_gitlab_source:) - { + # Overridden in EE + def ide_data(project:, fork_info:, params:) + base_data = { 'can-use-new-web-ide' => can_use_new_web_ide?.to_s, 'use-new-web-ide' => use_new_web_ide?.to_s, 'new-web-ide-help-page-path' => help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'), 'user-preferences-path' => profile_preferences_path, - 'branch-name' => branch, - 'file-path' => path, - 'fork-info' => fork_info&.to_json, 'editor-font-src-url' => font_url('jetbrains-mono/JetBrainsMono.woff2'), 'editor-font-family' => 'JetBrains Mono', - 'editor-font-format' => 'woff2', - 'merge-request' => merge_request, - 'learn-gitlab-source' => (!!learn_gitlab_source).to_s + 'editor-font-format' => 'woff2' }.merge(use_new_web_ide? ? new_ide_data(project: project) : legacy_ide_data(project: project)) + + return base_data unless project + + base_data.merge( + 'fork-info' => fork_info&.to_json, + 'branch-name' => params[:branch], + 'file-path' => params[:path], + 'merge-request' => params[:merge_request_id] + ) end def can_use_new_web_ide? @@ -77,3 +82,5 @@ module IdeHelper current_user.dismissed_callout?(feature_name: 'web_ide_ci_environments_guidance') end end + +IdeHelper.prepend_mod_with('IdeHelper') diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 81d083bd082..0d93aff2bae 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -156,7 +156,7 @@ module IssuablesHelper end output << content_tag(:strong) do - author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline") + author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline-block") author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-inline d-sm-none") author_output << issuable_meta_author_slot(issuable.author, css_class: 'ml-1') diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index 89211ed6a3e..201007863b2 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -2,7 +2,7 @@ module Nav module NewDropdownHelper - def new_dropdown_view_model(group:, project:, with_context: false) + def new_dropdown_view_model(group:, project:) return unless current_user menu_sections = [] @@ -10,10 +10,8 @@ module Nav if project&.persisted? menu_sections.push(project_menu_section(project)) - data[:context] = project if with_context elsif group&.persisted? menu_sections.push(group_menu_section(group)) - data[:context] = group if with_context end menu_sections.push(general_menu_section) diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb index 0d98c5a176a..17b225c5e9b 100644 --- a/app/mailers/previews/notify_preview.rb +++ b/app/mailers/previews/notify_preview.rb @@ -60,6 +60,14 @@ class NotifyPreview < ActionMailer::Preview end end + def access_token_created_email + Notify.access_token_created_email(user, 'token_name').message + end + + def access_token_revoked_email + Notify.access_token_revoked_email(user, 'token_name').message + end + def new_mention_in_merge_request_email Notify.new_mention_in_merge_request_email(user.id, merge_request.id, user.id).message end diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 89a3d269a43..92ab2e1af25 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -177,6 +177,8 @@ module Ci where(file_type: self.erasable_file_types) end + scope :non_trace, -> { where.not(file_type: [:trace]) } + scope :downloadable, -> { where(file_type: DOWNLOADABLE_TYPES) } scope :unlocked, -> { joins(job: :pipeline).merge(::Ci::Pipeline.unlocked) } scope :order_expired_asc, -> { order(expire_at: :asc) } diff --git a/app/services/ci/job_artifacts/bulk_delete_by_project_service.rb b/app/services/ci/job_artifacts/bulk_delete_by_project_service.rb index 7862774473c..738fa19e29b 100644 --- a/app/services/ci/job_artifacts/bulk_delete_by_project_service.rb +++ b/app/services/ci/job_artifacts/bulk_delete_by_project_service.rb @@ -30,10 +30,7 @@ module Ci return ServiceResponse.error(message: 'Not all artifacts belong to requested project') end - result = Ci::JobArtifacts::DestroyBatchService.new( - job_artifact_scope, - skip_trace_artifacts: false - ).execute + result = Ci::JobArtifacts::DestroyBatchService.new(job_artifact_scope).execute destroyed_artifacts_count = result.fetch(:destroyed_artifacts_count) destroyed_ids = result.fetch(:destroyed_ids) diff --git a/app/services/ci/job_artifacts/destroy_all_expired_service.rb b/app/services/ci/job_artifacts/destroy_all_expired_service.rb index 30683475ad2..57b95e59d7d 100644 --- a/app/services/ci/job_artifacts/destroy_all_expired_service.rb +++ b/app/services/ci/job_artifacts/destroy_all_expired_service.rb @@ -35,7 +35,7 @@ module Ci def destroy_unlocked_job_artifacts loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do - artifacts = Ci::JobArtifact.expired_before(@start_at).artifact_unlocked.limit(BATCH_SIZE) + artifacts = Ci::JobArtifact.expired_before(@start_at).non_trace.artifact_unlocked.limit(BATCH_SIZE) service_response = destroy_batch(artifacts) @removed_artifacts_count += service_response[:destroyed_artifacts_count] end diff --git a/app/services/ci/job_artifacts/destroy_batch_service.rb b/app/services/ci/job_artifacts/destroy_batch_service.rb index 94ecfa96be0..81cbeb31711 100644 --- a/app/services/ci/job_artifacts/destroy_batch_service.rb +++ b/app/services/ci/job_artifacts/destroy_batch_service.rb @@ -17,11 +17,10 @@ module Ci # +pick_up_at+:: When to pick up for deletion of files # Returns: # +Hash+:: A hash with status and destroyed_artifacts_count keys - def initialize(job_artifacts, pick_up_at: nil, skip_projects_on_refresh: false, skip_trace_artifacts: true) + def initialize(job_artifacts, pick_up_at: nil, skip_projects_on_refresh: false) @job_artifacts = job_artifacts.with_destroy_preloads.to_a @pick_up_at = pick_up_at @skip_projects_on_refresh = skip_projects_on_refresh - @skip_trace_artifacts = skip_trace_artifacts @destroyed_ids = [] end @@ -33,8 +32,6 @@ module Ci track_artifacts_undergoing_stats_refresh end - exclude_trace_artifacts if @skip_trace_artifacts - if @job_artifacts.empty? return success(destroyed_ids: @destroyed_ids, destroyed_artifacts_count: 0, statistics_updates: {}) end @@ -119,11 +116,6 @@ module Ci end end - # Traces should never be destroyed. - def exclude_trace_artifacts - _trace_artifacts, @job_artifacts = @job_artifacts.partition(&:trace?) - end - def track_artifacts_undergoing_stats_refresh project_ids = @job_artifacts.find_all do |artifact| artifact.project.refreshing_build_artifacts_size? diff --git a/app/services/releases/links/base_service.rb b/app/services/releases/links/base_service.rb index 939de982db4..8bab258f80a 100644 --- a/app/services/releases/links/base_service.rb +++ b/app/services/releases/links/base_service.rb @@ -2,6 +2,10 @@ module Releases module Links + REASON_BAD_REQUEST = :bad_request + REASON_NOT_FOUND = :not_found + REASON_FORBIDDEN = :forbidden + class BaseService attr_accessor :release, :current_user, :params diff --git a/app/services/releases/links/create_service.rb b/app/services/releases/links/create_service.rb index c73c9f40254..94823c54596 100644 --- a/app/services/releases/links/create_service.rb +++ b/app/services/releases/links/create_service.rb @@ -4,14 +4,14 @@ module Releases module Links class CreateService < BaseService def execute - return ServiceResponse.error(message: _('Access Denied')) unless allowed? + return ServiceResponse.error(reason: REASON_FORBIDDEN, message: _('Access Denied')) unless allowed? link = release.links.create(allowed_params) if link.persisted? ServiceResponse.success(payload: { link: link }) else - ServiceResponse.error(message: link.errors.full_messages) + ServiceResponse.error(reason: REASON_BAD_REQUEST, message: link.errors.full_messages) end end diff --git a/app/services/releases/links/destroy_service.rb b/app/services/releases/links/destroy_service.rb index 9edde2f357b..1c1158017bb 100644 --- a/app/services/releases/links/destroy_service.rb +++ b/app/services/releases/links/destroy_service.rb @@ -4,13 +4,13 @@ module Releases module Links class DestroyService < BaseService def execute(link) - return ServiceResponse.error(message: _('Access Denied')) unless allowed? - return ServiceResponse.error(message: _('Link does not exist')) unless link + return ServiceResponse.error(reason: REASON_FORBIDDEN, message: _('Access Denied')) unless allowed? + return ServiceResponse.error(reason: REASON_NOT_FOUND, message: _('Link does not exist')) unless link if link.destroy ServiceResponse.success(payload: { link: link }) else - ServiceResponse.error(message: link.errors.full_messages) + ServiceResponse.error(reason: REASON_BAD_REQUEST, message: link.errors.full_messages) end end diff --git a/app/services/releases/links/update_service.rb b/app/services/releases/links/update_service.rb index f50cde5c5a9..c29de86f31b 100644 --- a/app/services/releases/links/update_service.rb +++ b/app/services/releases/links/update_service.rb @@ -4,13 +4,13 @@ module Releases module Links class UpdateService < BaseService def execute(link) - return ServiceResponse.error(message: _('Access Denied')) unless allowed? - return ServiceResponse.error(message: _('Link does not exist')) unless link + return ServiceResponse.error(reason: REASON_FORBIDDEN, message: _('Access Denied')) unless allowed? + return ServiceResponse.error(reason: REASON_NOT_FOUND, message: _('Link does not exist')) unless link if link.update(allowed_params) ServiceResponse.success(payload: { link: link }) else - ServiceResponse.error(message: link.errors.full_messages) + ServiceResponse.error(reason: REASON_BAD_REQUEST, message: link.errors.full_messages) end end diff --git a/app/views/groups/_invite_members_top_nav_link.html.haml b/app/views/groups/_invite_members_top_nav_link.html.haml index e419c479bca..35a8d4d9944 100644 --- a/app/views/groups/_invite_members_top_nav_link.html.haml +++ b/app/views/groups/_invite_members_top_nav_link.html.haml @@ -3,5 +3,3 @@ - data[:icon] = local_assigns.fetch(:icon) .js-invite-members-trigger{ data: data } - -= render 'groups/invite_members_modal', group: local_assigns.fetch(:context) diff --git a/app/views/ide/_show.html.haml b/app/views/ide/_show.html.haml index 091d7e7a4f1..eb6d5668807 100644 --- a/app/views/ide/_show.html.haml +++ b/app/views/ide/_show.html.haml @@ -7,11 +7,6 @@ - content_for :prefetch_asset_tags do - webpack_preload_asset_tag('monaco') -- data = ide_data(project: @project, - branch: @branch, - path: @path, - merge_request: @merge_request, - fork_info: @fork_info, - learn_gitlab_source: @learn_gitlab_source) +- data = ide_data(project: @project, fork_info: @fork_info, params: params) = render partial: 'shared/ide_root', locals: { data: data, loading_text: _('Loading the GitLab IDE...') } diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 28579c7f7ea..40ec1ff199b 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -16,6 +16,9 @@ :plain window.uploads_path = "#{group_uploads_path(@group)}"; +- content_for :before_content do + = render 'groups/invite_members_modal', group: @group + = dispensable_render_if_exists "shared/web_hooks/group_web_hook_disabled_alert" = dispensable_render_if_exists "shared/free_user_cap_alert", source: @group diff --git a/app/views/layouts/header/_new_dropdown.html.haml b/app/views/layouts/header/_new_dropdown.html.haml index 42c2fd645da..50a2b45aa7e 100644 --- a/app/views/layouts/header/_new_dropdown.html.haml +++ b/app/views/layouts/header/_new_dropdown.html.haml @@ -1,4 +1,4 @@ -- view_model = new_dropdown_view_model(project: @project, group: @group, with_context: true) +- view_model = new_dropdown_view_model(project: @project, group: @group) - menu_sections = view_model.fetch(:menu_sections) - title = view_model.fetch(:title) - show_headers = menu_sections.length > 1 @@ -28,8 +28,7 @@ %li< - if menu_item.fetch(:partial).present? = render partial: menu_item.fetch(:partial), - locals: { context: view_model[:context], - display_text: menu_item.fetch(:title), + locals: { display_text: menu_item.fetch(:title), icon: menu_item.fetch(:icon), data: menu_item.fetch(:data) } - else diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 6ad6696b313..09fa8575106 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -18,6 +18,9 @@ :plain window.uploads_path = "#{project_uploads_path(project)}"; +- content_for :before_content do + = render 'projects/invite_members_modal', project: @project + = dispensable_render_if_exists "shared/web_hooks/web_hook_disabled_alert" = dispensable_render_if_exists "projects/free_user_cap_alert", project: @project diff --git a/app/views/projects/_invite_members_top_nav_link.html.haml b/app/views/projects/_invite_members_top_nav_link.html.haml index d2e68325a09..35a8d4d9944 100644 --- a/app/views/projects/_invite_members_top_nav_link.html.haml +++ b/app/views/projects/_invite_members_top_nav_link.html.haml @@ -3,5 +3,3 @@ - data[:icon] = local_assigns.fetch(:icon) .js-invite-members-trigger{ data: data } - -= render 'projects/invite_members_modal', project: local_assigns.fetch(:context) diff --git a/app/views/projects/branch_rules/_show.html.haml b/app/views/projects/branch_rules/_show.html.haml index 32b093bb95c..c0362f3e85d 100644 --- a/app/views/projects/branch_rules/_show.html.haml +++ b/app/views/projects/branch_rules/_show.html.haml @@ -10,6 +10,7 @@ = expanded ? _('Collapse') : _('Expand') %p = _('Define rules for who can push, merge, and the required approvals for each branch.') + = link_to(_('Leave feadback.'), 'https://gitlab.com/gitlab-org/gitlab/-/issues/388149', target: '_blank', rel: 'noopener noreferrer') .settings-content.gl-pr-0 #js-branch-rules{ data: { project_path: @project.full_path, branch_rules_path: project_settings_repository_branch_rules_path(@project), show_code_owners: show_code_owners.to_s, show_status_checks: show_status_checks.to_s, show_approvers: show_approvers.to_s } } |