summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-07 15:10:50 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-07 15:10:50 +0000
commit807c4eae46f96ccd54ce1d8d13f4547eda017267 (patch)
tree190aaf8d8c0a766fa7fc396355fd5e0d865db889 /app
parentebe0e306bbd6e913763bf1865b7778c001994e31 (diff)
downloadgitlab-ce-807c4eae46f96ccd54ce1d8d13f4547eda017267.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/artifacts/components/job_artifacts_table.vue8
-rw-r--r--app/assets/javascripts/work_items/components/notes/activity_filter.vue112
-rw-r--r--app/assets/javascripts/work_items/components/notes/activity_sort.vue99
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue61
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue67
-rw-r--r--app/assets/javascripts/work_items/components/work_item_notes.vue73
-rw-r--r--app/assets/javascripts/work_items/constants.js6
-rw-r--r--app/controllers/ide_controller.rb12
-rw-r--r--app/helpers/ide_helper.rb23
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/helpers/nav/new_dropdown_helper.rb4
-rw-r--r--app/mailers/previews/notify_preview.rb8
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--app/services/ci/job_artifacts/bulk_delete_by_project_service.rb5
-rw-r--r--app/services/ci/job_artifacts/destroy_all_expired_service.rb2
-rw-r--r--app/services/ci/job_artifacts/destroy_batch_service.rb10
-rw-r--r--app/services/releases/links/base_service.rb4
-rw-r--r--app/services/releases/links/create_service.rb4
-rw-r--r--app/services/releases/links/destroy_service.rb6
-rw-r--r--app/services/releases/links/update_service.rb6
-rw-r--r--app/views/groups/_invite_members_top_nav_link.html.haml2
-rw-r--r--app/views/ide/_show.html.haml7
-rw-r--r--app/views/layouts/group.html.haml3
-rw-r--r--app/views/layouts/header/_new_dropdown.html.haml5
-rw-r--r--app/views/layouts/project.html.haml3
-rw-r--r--app/views/projects/_invite_members_top_nav_link.html.haml2
-rw-r--r--app/views/projects/branch_rules/_show.html.haml1
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 } }