summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 09:10:09 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 09:10:09 +0000
commiteeb25534bae1021f5b7940138ee56dea8fc79949 (patch)
tree26cf3d6a4ac582ed3d0d3a20b82a200da580f1e3
parent75621c94b5dbe233edd72c3d8cc602fed25e84d2 (diff)
downloadgitlab-ce-eeb25534bae1021f5b7940138ee56dea8fc79949.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/rspec/avoid_conditional_statements.yml1
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue2
-rw-r--r--app/assets/javascripts/graphql_shared/issuable_client.js10
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue10
-rw-r--r--app/assets/javascripts/repository/components/fork_info.vue2
-rw-r--r--app/assets/javascripts/work_items/components/notes/system_note.vue107
-rw-r--r--app/assets/javascripts/work_items/components/work_item_description.vue4
-rw-r--r--app/assets/javascripts/work_items/components/work_item_notes.vue6
-rw-r--r--app/assets/javascripts/work_items/graphql/notes/create_work_item_note.mutation.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/notes/update_work_item_note.mutation.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/notes/work_item_discussion_note.fragment.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/notes/work_item_note.fragment.graphql7
-rw-r--r--app/assets/javascripts/work_items/graphql/notes/work_item_note_updated.subscription.graphql2
-rw-r--r--app/assets/javascripts/work_items/graphql/notes/work_item_notes_by_iid.query.graphql2
-rw-r--r--app/assets/javascripts/work_items/mixins/description_version_history.js14
-rw-r--r--app/assets/javascripts/work_items/notes/collapse_utils.js92
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--data/removals/16_0/16-0-source-code-approvals-endpoint.yml30
-rw-r--r--doc/api/merge_request_approvals.md14
-rw-r--r--doc/api/search_admin.md125
-rw-r--r--doc/update/removals.md10
-rw-r--r--doc/user/group/epics/index.md7
-rw-r--r--doc/user/project/issues/index.md7
-rw-r--r--spec/features/snippets/explore_spec.rb20
-rw-r--r--spec/frontend/boards/components/board_content_spec.js24
-rw-r--r--spec/frontend/work_items/components/notes/system_note_spec.js96
-rw-r--r--spec/frontend/work_items/components/notes/work_item_add_note_spec.js2
-rw-r--r--spec/frontend/work_items/components/work_item_labels_spec.js2
-rw-r--r--spec/frontend/work_items/mock_data.js353
-rw-r--r--spec/frontend/work_items/notes/collapse_utils_spec.js29
31 files changed, 791 insertions, 197 deletions
diff --git a/.rubocop_todo/rspec/avoid_conditional_statements.yml b/.rubocop_todo/rspec/avoid_conditional_statements.yml
index 4817708667a..4a4ba87eab3 100644
--- a/.rubocop_todo/rspec/avoid_conditional_statements.yml
+++ b/.rubocop_todo/rspec/avoid_conditional_statements.yml
@@ -77,7 +77,6 @@ RSpec/AvoidConditionalStatements:
- 'spec/features/projects/tree/create_file_spec.rb'
- 'spec/features/projects_spec.rb'
- 'spec/features/search/user_uses_header_search_field_spec.rb'
- - 'spec/features/snippets/explore_spec.rb'
- 'spec/features/usage_stats_consent_spec.rb'
- 'spec/features/users/login_spec.rb'
- 'spec/features/users/overview_spec.rb'
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 8304dfef527..9416cbf1884 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -130,7 +130,7 @@ export default {
:list="list"
:filters="filterParams"
:data-draggable-item-type="$options.draggableItemTypes.list"
- :class="{ 'gl-xs-display-none!': addColumnFormVisible }"
+ :class="{ 'gl-display-none! gl-sm-display-inline-block!': addColumnFormVisible }"
@setActiveList="$emit('setActiveList', $event)"
/>
diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js
index d0b0a485fe6..706bfc9bdac 100644
--- a/app/assets/javascripts/graphql_shared/issuable_client.js
+++ b/app/assets/javascripts/graphql_shared/issuable_client.js
@@ -47,6 +47,16 @@ export const config = {
},
},
},
+ DescriptionVersion: {
+ fields: {
+ startVersionId: {
+ read() {
+ // we need to set this when fetching the diff in the last 10 mins , the starting diff will be the very first one , so need to save it
+ return '';
+ },
+ },
+ },
+ },
WorkItem: {
fields: {
// widgets policy because otherwise the subscriptions invalidate the cache
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue
index f95ec4336dc..80df8ef81e6 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/packages_cleanup_policy_form.vue
@@ -139,7 +139,7 @@ export default {
:form-options="$options.formOptions.keepNDuplicatedPackageFiles"
:label="$options.i18n.KEEP_N_DUPLICATED_PACKAGE_FILES_LABEL"
:description="$options.i18n.KEEP_N_DUPLICATED_PACKAGE_FILES_DESCRIPTION"
- dropdown-class="gl-md-max-w-50p gl-sm-pr-5"
+ dropdown-class="gl-md-max-w-50p"
name="keep-n-duplicated-package-files"
data-testid="keep-n-duplicated-package-files-dropdown"
@input="onModelChange($event, 'keepNDuplicatedPackageFiles')"
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 64c363dd721..031910b1cdb 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -552,7 +552,7 @@ export default {
<template>
<div>
<div
- class="project-visibility-setting gl-border-1 gl-border-solid gl-border-gray-100 gl-py-3 gl-px-7 gl-sm-pr-5 gl-sm-pl-5"
+ class="project-visibility-setting gl-border-1 gl-border-solid gl-border-gray-100 gl-py-3 gl-px-5"
>
<project-setting-row
ref="project-visibility-settings"
@@ -647,7 +647,7 @@ export default {
</div>
<div
:class="{ 'highlight-changes': highlightChangesClass }"
- class="gl-border-1 gl-border-solid gl-border-t-none gl-border-gray-100 gl-mb-5 gl-py-3 gl-px-7 gl-sm-pr-5 gl-sm-pl-5 gl-bg-gray-10"
+ class="gl-border-1 gl-border-solid gl-border-t-none gl-border-gray-100 gl-mb-5 gl-py-3 gl-px-5 gl-bg-gray-10"
>
<project-setting-row
ref="issues-settings"
@@ -693,7 +693,7 @@ export default {
name="project[project_feature_attributes][repository_access_level]"
/>
</project-setting-row>
- <div class="project-feature-setting-group gl-pl-7 gl-sm-pl-5">
+ <div class="project-feature-setting-group gl-pl-5 gl-md-pl-7">
<project-setting-row
ref="merge-request-settings"
:label="$options.i18n.mergeRequestsLabel"
@@ -875,7 +875,7 @@ export default {
/>
<div
v-if="packageRegistryApiForEveryoneEnabledShown"
- class="project-feature-setting-group gl-pl-7 gl-sm-pl-5 gl-my-3"
+ class="project-feature-setting-group gl-pl-5 gl-md-pl-7 gl-my-3"
>
<project-setting-row
:label="$options.i18n.packageRegistryForEveryoneLabel"
@@ -932,7 +932,7 @@ export default {
</project-setting-row>
<div
v-if="!glFeatures.removeMonitorMetrics"
- class="project-feature-setting-group gl-pl-7 gl-sm-pl-5"
+ class="project-feature-setting-group gl-pl-5 gl-md-pl-7"
>
<project-setting-row
ref="metrics-visibility-settings"
diff --git a/app/assets/javascripts/repository/components/fork_info.vue b/app/assets/javascripts/repository/components/fork_info.vue
index 1da445a7906..e4a10784bd5 100644
--- a/app/assets/javascripts/repository/components/fork_info.vue
+++ b/app/assets/javascripts/repository/components/fork_info.vue
@@ -314,7 +314,7 @@ export default {
>
{{ $options.i18n.inaccessibleProject }}
</div>
- <div class="gl-display-flex gl-xs-display-none!">
+ <div class="gl-display-none gl-sm-display-flex">
<gl-button
v-if="hasCreateMrButton"
class="gl-ml-4"
diff --git a/app/assets/javascripts/work_items/components/notes/system_note.vue b/app/assets/javascripts/work_items/components/notes/system_note.vue
index f8dfa1c7f01..534fbbe96ac 100644
--- a/app/assets/javascripts/work_items/components/notes/system_note.vue
+++ b/app/assets/javascripts/work_items/components/notes/system_note.vue
@@ -19,8 +19,7 @@ import { GlButton, GlSkeletonLoader, GlTooltipDirective, GlIcon } from '@gitlab/
import $ from 'jquery';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import SafeHtml from '~/vue_shared/directives/safe_html';
-import descriptionVersionHistoryMixin from 'ee_else_ce/notes/mixins/description_version_history';
-import axios from '~/lib/utils/axios_utils';
+import descriptionVersionHistoryMixin from 'ee_else_ce/work_items/mixins/description_version_history';
import { getLocationHash } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
@@ -28,8 +27,6 @@ import NoteHeader from '~/notes/components/note_header.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
-const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
-
export default {
i18n: {
deleteButtonLabel: __('Remove description history'),
@@ -60,15 +57,13 @@ export default {
showLines: false,
loadingDiff: false,
isLoadingDescriptionVersion: false,
+ descriptionVersions: {},
};
},
computed: {
targetNoteHash() {
return getLocationHash();
},
- descriptionVersions() {
- return [];
- },
noteAnchorId() {
return `note_${this.noteId}`;
},
@@ -78,42 +73,22 @@ export default {
toggleIcon() {
return this.expanded ? 'chevron-up' : 'chevron-down';
},
- // following 2 methods taken from code in `collapseLongCommitList` of notes.js:
actionTextHtml() {
return $(this.note.bodyHtml).unwrap().html();
},
- hasMoreCommits() {
- return $(this.note.bodyHtml).filter('ul').children().length > MAX_VISIBLE_COMMIT_LIST_COUNT;
- },
- descriptionVersion() {
- return this.descriptionVersions[this.note.description_version_id];
+ descriptionVersionId() {
+ return getIdFromGraphQLId(this.systemNoteDescriptionVersion?.id);
},
noteId() {
return getIdFromGraphQLId(this.note.id);
},
+ descriptionVersion() {
+ return this.descriptionVersions[this.descriptionVersionId];
+ },
},
mounted() {
renderGFM(this.$refs['gfm-content']);
},
- methods: {
- fetchDescriptionVersion() {},
- softDeleteDescriptionVersion() {},
-
- async toggleDiff() {
- this.showLines = !this.showLines;
-
- if (!this.lines.length) {
- this.loadingDiff = true;
- const { data } = await axios.get(this.note.outdated_line_change_path);
-
- this.lines = data.map((l) => ({
- ...l,
- rich_text: l.rich_text.replace(/^[+ -]/, ''),
- }));
- this.loadingDiff = false;
- }
- },
- },
safeHtmlConfig: {
ADD_TAGS: ['use'], // to support icon SVGs
},
@@ -141,10 +116,7 @@ export default {
:is-system-note="true"
>
<span ref="gfm-content" v-safe-html="actionTextHtml"></span>
- <template
- v-if="canSeeDescriptionVersion || note.outdated_line_change_path"
- #extra-controls
- >
+ <template v-if="canSeeDescriptionVersion" #extra-controls>
&middot;
<gl-button
v-if="canSeeDescriptionVersion"
@@ -155,36 +127,20 @@ export default {
@click="toggleDescriptionVersion"
>{{ __('Compare with previous version') }}</gl-button
>
- <gl-button
- v-if="note.outdated_line_change_path"
- :icon="showLines ? 'chevron-up' : 'chevron-down'"
- variant="link"
- data-testid="outdated-lines-change-btn"
- class="gl-vertical-align-text-bottom gl-font-sm!"
- @click="toggleDiff"
- >
- {{ __('Compare changes') }}
- </gl-button>
</template>
</note-header>
</div>
<div class="note-body">
- <div
- v-safe-html="note.bodyHtml"
- :class="{ 'system-note-commit-list': hasMoreCommits, 'hide-shade': expanded }"
- class="note-text md"
- ></div>
- <div v-if="hasMoreCommits" class="flex-list">
- <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded">
- <gl-icon :name="toggleIcon" :size="8" class="gl-mr-2" />
- <span>{{ __('Toggle commit list') }}</span>
- </div>
- </div>
- <div v-if="shouldShowDescriptionVersion" class="description-version pt-2">
+ <div v-if="shouldShowDescriptionVersion" class="description-version gl-pt-3! gl-pl-4">
<pre v-if="isLoadingDescriptionVersion" class="loading-state">
<gl-skeleton-loader />
</pre>
- <pre v-else v-safe-html="descriptionVersion" class="wrapper mt-2"></pre>
+ <pre
+ v-else
+ v-safe-html="descriptionVersion"
+ data-testid="description-version-diff"
+ class="wrapper gl-mt-3"
+ ></pre>
<gl-button
v-if="displayDeleteButton"
v-gl-tooltip
@@ -198,39 +154,6 @@ export default {
@click="deleteDescriptionVersion"
/>
</div>
- <div
- v-if="lines.length && showLines"
- class="diff-content outdated-lines-wrapper gl-border-solid gl-border-1 gl-border-gray-200 gl-mt-4 gl-rounded-small gl-overflow-hidden"
- >
- <table
- :class="$options.userColorSchemeClass"
- class="code js-syntax-highlight"
- data-testid="outdated-lines"
- >
- <tr v-for="line in lines" v-once :key="line.line_code" class="line_holder">
- <td
- :class="line.type"
- class="diff-line-num old_line gl-border-bottom-0! gl-border-top-0! gl-border-0! gl-rounded-0!"
- >
- {{ line.old_line }}
- </td>
- <td
- :class="line.type"
- class="diff-line-num new_line gl-border-bottom-0! gl-border-top-0!"
- >
- {{ line.new_line }}
- </td>
- <td
- :class="line.type"
- class="line_content gl-display-table-cell! gl-border-0! gl-rounded-0!"
- v-html="line.rich_text /* eslint-disable-line vue/no-v-html */"
- ></td>
- </tr>
- </table>
- </div>
- <div v-else-if="showLines" class="mt-4">
- <gl-skeleton-loader />
- </div>
</div>
</div>
</timeline-entry-item>
diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue
index a4cbc430b84..279acc98cd4 100644
--- a/app/assets/javascripts/work_items/components/work_item_description.vue
+++ b/app/assets/javascripts/work_items/components/work_item_description.vue
@@ -229,13 +229,13 @@ export default {
<div>
<gl-form-group
v-if="isEditing"
- class="gl-mb-5 gl-border-t gl-pt-6"
+ class="gl-mb-5 gl-border-t gl-pt-6 common-note-form"
:label="__('Description')"
label-for="work-item-description"
>
<markdown-editor
v-if="glFeatures.workItemsMvc"
- class="gl-my-3 common-note-form"
+ class="gl-my-5"
:value="descriptionText"
:render-markdown-path="markdownPreviewPath"
:markdown-docs-path="$options.markdownDocsPath"
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 092b90a5731..8fc460294e6 100644
--- a/app/assets/javascripts/work_items/components/work_item_notes.vue
+++ b/app/assets/javascripts/work_items/components/work_item_notes.vue
@@ -21,6 +21,7 @@ import {
updateCacheAfterDeletingNote,
} from '~/work_items/graphql/cache_utils';
import { getLocationHash } from '~/lib/utils/url_utility';
+import { collapseSystemNotes } from '~/work_items/notes/collapse_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';
@@ -128,7 +129,9 @@ export default {
notesArray() {
const notes = this.workItemNotes?.nodes || [];
- const visibleNotes = notes.filter((note) => {
+ let visibleNotes = collapseSystemNotes(notes);
+
+ visibleNotes = visibleNotes.filter((note) => {
const isSystemNote = this.isSystemNote(note);
if (this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS && isSystemNote) {
@@ -145,6 +148,7 @@ export default {
if (this.sortOrder === DESC) {
return [...visibleNotes].reverse();
}
+
return visibleNotes;
},
commentsDisabled() {
diff --git a/app/assets/javascripts/work_items/graphql/notes/create_work_item_note.mutation.graphql b/app/assets/javascripts/work_items/graphql/notes/create_work_item_note.mutation.graphql
index 5050aa7cbda..3286895215f 100644
--- a/app/assets/javascripts/work_items/graphql/notes/create_work_item_note.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/notes/create_work_item_note.mutation.graphql
@@ -1,4 +1,4 @@
-#import "./work_item_note.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/notes/work_item_note.fragment.graphql"
mutation createWorkItemNote($input: CreateNoteInput!) {
createNote(input: $input) {
diff --git a/app/assets/javascripts/work_items/graphql/notes/update_work_item_note.mutation.graphql b/app/assets/javascripts/work_items/graphql/notes/update_work_item_note.mutation.graphql
index 3da8e7677e4..eb52eb912e7 100644
--- a/app/assets/javascripts/work_items/graphql/notes/update_work_item_note.mutation.graphql
+++ b/app/assets/javascripts/work_items/graphql/notes/update_work_item_note.mutation.graphql
@@ -1,4 +1,4 @@
-#import "./work_item_note.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/notes/work_item_note.fragment.graphql"
mutation updateWorkItemNote($input: UpdateNoteInput!) {
updateNote(input: $input) {
diff --git a/app/assets/javascripts/work_items/graphql/notes/work_item_discussion_note.fragment.graphql b/app/assets/javascripts/work_items/graphql/notes/work_item_discussion_note.fragment.graphql
index 58561e33e53..635faf27892 100644
--- a/app/assets/javascripts/work_items/graphql/notes/work_item_discussion_note.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/notes/work_item_discussion_note.fragment.graphql
@@ -1,5 +1,5 @@
#import "~/graphql_shared/fragments/user.fragment.graphql"
-#import "./work_item_note.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/notes/work_item_note.fragment.graphql"
fragment WorkItemDiscussionNote on Note {
id
diff --git a/app/assets/javascripts/work_items/graphql/notes/work_item_note.fragment.graphql b/app/assets/javascripts/work_items/graphql/notes/work_item_note.fragment.graphql
index 93616c39e55..973c6fde474 100644
--- a/app/assets/javascripts/work_items/graphql/notes/work_item_note.fragment.graphql
+++ b/app/assets/javascripts/work_items/graphql/notes/work_item_note.fragment.graphql
@@ -28,4 +28,11 @@ fragment WorkItemNote on Note {
resolveNote
repositionNote
}
+ systemNoteMetadata {
+ id
+ descriptionVersion {
+ id
+ description
+ }
+ }
}
diff --git a/app/assets/javascripts/work_items/graphql/notes/work_item_note_updated.subscription.graphql b/app/assets/javascripts/work_items/graphql/notes/work_item_note_updated.subscription.graphql
index c68d5f491cf..1a6f4e44ee0 100644
--- a/app/assets/javascripts/work_items/graphql/notes/work_item_note_updated.subscription.graphql
+++ b/app/assets/javascripts/work_items/graphql/notes/work_item_note_updated.subscription.graphql
@@ -1,4 +1,4 @@
-#import "./work_item_note.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/notes/work_item_note.fragment.graphql"
subscription workItemNoteUpdated($noteableId: NoteableID) {
workItemNoteUpdated(noteableId: $noteableId) {
diff --git a/app/assets/javascripts/work_items/graphql/notes/work_item_notes_by_iid.query.graphql b/app/assets/javascripts/work_items/graphql/notes/work_item_notes_by_iid.query.graphql
index 6b37c68cb43..6022b280d72 100644
--- a/app/assets/javascripts/work_items/graphql/notes/work_item_notes_by_iid.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/notes/work_item_notes_by_iid.query.graphql
@@ -1,5 +1,5 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
-#import "./work_item_note.fragment.graphql"
+#import "ee_else_ce/work_items/graphql/notes/work_item_note.fragment.graphql"
query workItemNotesByIid($fullPath: ID!, $iid: String, $after: String, $pageSize: Int) {
workspace: project(fullPath: $fullPath) {
diff --git a/app/assets/javascripts/work_items/mixins/description_version_history.js b/app/assets/javascripts/work_items/mixins/description_version_history.js
new file mode 100644
index 00000000000..d1006e37a70
--- /dev/null
+++ b/app/assets/javascripts/work_items/mixins/description_version_history.js
@@ -0,0 +1,14 @@
+// Placeholder for GitLab FOSS
+// Actual implementation: ee/app/assets/javascripts/notes/mixins/description_version_history.js
+export default {
+ computed: {
+ canSeeDescriptionVersion() {},
+ displayDeleteButton() {},
+ shouldShowDescriptionVersion() {},
+ descriptionVersionToggleIcon() {},
+ },
+ methods: {
+ toggleDescriptionVersion() {},
+ deleteDescriptionVersion() {},
+ },
+};
diff --git a/app/assets/javascripts/work_items/notes/collapse_utils.js b/app/assets/javascripts/work_items/notes/collapse_utils.js
new file mode 100644
index 00000000000..db7b4530e2a
--- /dev/null
+++ b/app/assets/javascripts/work_items/notes/collapse_utils.js
@@ -0,0 +1,92 @@
+import { DESCRIPTION_TYPE, TIME_DIFFERENCE_VALUE } from '~/notes/constants';
+
+/**
+ * Checks the time difference between two notes from their 'created_at' dates
+ * returns an integer
+ */
+export const getTimeDifferenceInMinutes = (noteBeginning, noteEnd) => {
+ const descriptionNoteBegin = new Date(noteBeginning.createdAt);
+ const descriptionNoteEnd = new Date(noteEnd.createdAt);
+ const timeDifferenceMinutes = (descriptionNoteEnd - descriptionNoteBegin) / 1000 / 60;
+
+ return Math.ceil(timeDifferenceMinutes);
+};
+
+/**
+ * Checks if a note is a system note and if the content is description
+ *
+ * @param {Object} note
+ * @returns {Boolean}
+ */
+export const isDescriptionSystemNote = (note) => {
+ return note.system && note.body === DESCRIPTION_TYPE;
+};
+
+/**
+ * Collapses the system notes of a description type, e.g. Changed the description, n minutes ago
+ * the notes will collapse as long as they happen no more than 10 minutes away from each away
+ * in between the notes can be anything, another type of system note
+ * (such as 'changed the weight') or a comment.
+ *
+ * @param {Array} notes
+ * @returns {Array}
+ */
+export const collapseSystemNotes = (notes) => {
+ let lastDescriptionSystemNote = null;
+ let lastDescriptionSystemNoteIndex = -1;
+
+ return notes.reduce((acc, currentNote) => {
+ const note = currentNote.notes.nodes[0];
+ let lastStartVersionId = '';
+
+ if (isDescriptionSystemNote(note)) {
+ // is it the first one?
+ if (!lastDescriptionSystemNote) {
+ lastDescriptionSystemNote = note;
+ } else {
+ const timeDifferenceMinutes = getTimeDifferenceInMinutes(lastDescriptionSystemNote, note);
+
+ // are they less than 10 minutes apart from the same user?
+ if (
+ timeDifferenceMinutes > TIME_DIFFERENCE_VALUE ||
+ note.author.id !== lastDescriptionSystemNote.author.id ||
+ lastDescriptionSystemNote.systemNoteMetadata.descriptionVersion?.deleted
+ ) {
+ // update the previous system note
+ lastDescriptionSystemNote = note;
+ } else {
+ // set the first version to fetch grouped system note versions
+
+ lastStartVersionId = lastDescriptionSystemNote.systemNoteMetadata.descriptionVersion.id;
+
+ // delete the previous one
+ acc.splice(lastDescriptionSystemNoteIndex, 1);
+ }
+ }
+
+ // update the previous system note index
+ lastDescriptionSystemNoteIndex = acc.length;
+
+ acc.push({
+ notes: {
+ nodes: [
+ {
+ ...note,
+ systemNoteMetadata: {
+ ...note.systemNoteMetadata,
+ descriptionVersion: {
+ ...note.systemNoteMetadata.descriptionVersion,
+ startVersionId: lastStartVersionId,
+ },
+ },
+ },
+ ],
+ },
+ });
+ } else {
+ acc.push(currentNote);
+ }
+
+ return acc;
+ }, []);
+};
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 79a33316b1a..bc07c3f4370 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -83,7 +83,7 @@
= _('Updated')
= updated_tooltip
- .project-cell{ class: "#{css_class} gl-xs-display-none!" }
+ .project-cell{ class: "#{css_class} gl-display-none! gl-sm-display-table-cell!" }
.project-controls.gl-display-flex.gl-flex-direction-column.gl-align-items-flex-end.gl-w-full{ data: { testid: 'project_controls'} }
.controls.gl-display-flex.gl-align-items-center.gl-mb-2{ class: "#{css_controls_class} gl-pr-0!" }
- if show_pipeline_status_icon && last_pipeline.present?
diff --git a/data/removals/16_0/16-0-source-code-approvals-endpoint.yml b/data/removals/16_0/16-0-source-code-approvals-endpoint.yml
new file mode 100644
index 00000000000..786ed39566b
--- /dev/null
+++ b/data/removals/16_0/16-0-source-code-approvals-endpoint.yml
@@ -0,0 +1,30 @@
+# This is a template for announcing a feature removal or other important change.
+#
+# Please refer to the deprecation guidelines to confirm your understanding of GitLab's definitions.
+# https://docs.gitlab.com/ee/development/deprecation_guidelines/#terminology
+#
+# If this is a breaking change, it must happen in a major release.
+#
+# For more information please refer to the handbook documentation here:
+# https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-and-other-planned-breaking-change-announcements
+#
+# Please delete this line and above before submitting your merge request.
+#
+# REQUIRED FIELDS
+#
+- title: "`POST /projects/:id/merge_requests/:merge_request_iid/approvals` removed" # (required) Clearly explain the change. For example, "The `confidential` field for a `Note` is removed" or "CI/CD job names are limited to 250 characters."
+ announcement_milestone: "12.3" # (required) The milestone when this feature was deprecated.
+ removal_milestone: "16.0" # (required) The milestone when this feature is being removed.
+ breaking_change: true # (required) Change to false if this is not a breaking change.
+ reporter: tlinz # (required) GitLab username of the person reporting the removal
+ stage: create # (required) String value of the stage that the feature was created in. e.g., Growth
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353097 # (required) Link to the deprecation issue in GitLab
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ The `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. To change the approvals required for a merge request via the API, use the `/approval_rules` endpoint described in [Create merge request level rule](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule).
+#
+# OPTIONAL FIELDS
+#
+ tiers: [Premium, Ultimate] # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
+ documentation_url: https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule # (optional) This is a link to the current documentation page
+ image_url: # (optional) This is a link to a thumbnail image depicting the feature
+ video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md
index ccd79c697a0..19179bddb00 100644
--- a/doc/api/merge_request_approvals.md
+++ b/doc/api/merge_request_approvals.md
@@ -596,6 +596,20 @@ Supported attributes:
}
```
+<!--- start_remove The following content will be removed on remove_date: '2023-08-17' -->
+
+### Change approval configuration (removed)
+
+> - Endpoint `/approvals` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3.
+> - Endpoint `approvals` [disabled](https://gitlab.com/gitlab-org/gitlab/-/issues/353097) in GitLab 16.0 [with a flag](../administration/feature_flags.md) named `remove_deprecated_approvals`. Disabled by default.
+
+The endpoint `POST /projects/:id/merge_requests/:merge_request_iid/approvals` was
+deprecated in GitLab 12.3, and removed in GitLab 16.0. To change the approvals
+required for a merge request, use the `/approval_rules` endpoint described in
+[Create merge request level rule](#create-merge-request-level-rule) on this page.
+
+<!--- end_remove -->
+
### Get the approval state of merge requests
> Moved to GitLab Premium in 13.9.
diff --git a/doc/api/search_admin.md b/doc/api/search_admin.md
new file mode 100644
index 00000000000..9e1aa1a4439
--- /dev/null
+++ b/doc/api/search_admin.md
@@ -0,0 +1,125 @@
+---
+stage: Data Stores
+group: Global Search
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Search admin API **(PREMIUM SELF)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120751) in GitLab 16.1
+
+The search admin API returns information about [advanced search migrations](../integration/advanced_search/elasticsearch.md#advanced-search-migrations).
+
+You must have administrator access to use this API.
+
+## List all advanced search migrations
+
+Get a list of all advanced search migrations for the GitLab instance.
+
+```plaintext
+GET /admin/search/migrations
+```
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://primary.example.com/api/v4/admin/search/migrations"
+```
+
+Example response:
+
+```json
+[
+ {
+ "version": 20230427555555,
+ "name": "BackfillHiddenOnMergeRequests",
+ "started_at": "2023-05-12T01:35:05.469+00:00",
+ "completed_at": "2023-05-12T01:36:06.432+00:00",
+ "completed": true,
+ "obsolete": false,
+ "migration_state": {}
+ },
+ {
+ "version": 20230428500000,
+ "name": "AddSuffixProjectInWikiRid",
+ "started_at": "2023-05-04T18:59:43.542+00:00",
+ "completed_at": "2023-05-04T18:59:43.542+00:00",
+ "completed": false,
+ "obsolete": false,
+ "migration_state": {
+ "pause_indexing": true,
+ "slice": 1,
+ "task_id": null,
+ "max_slices": 5,
+ "retry_attempt": 0
+ }
+ },
+ {
+ "version": 20230503064300,
+ "name": "BackfillProjectPermissionsInBlobsUsingPermutations",
+ "started_at": "2023-05-03T16:04:44.074+00:00",
+ "completed_at": "2023-05-03T16:04:44.074+00:00",
+ "completed": true,
+ "obsolete": false,
+ "migration_state": {
+ "permutation_idx": 8,
+ "documents_remaining": 5,
+ "task_id": "I2_LXc-xQlOeu-KmjYpM8g:172820",
+ "documents_remaining_for_permutation": 0
+ }
+ }
+]
+```
+
+## Get an advanced search migration
+
+Get a single advanced search migration by providing the migration version or name.
+
+```plaintext
+GET /admin/search/mirations/:version_or_name
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+|-------------------|----------------|----------|--------------------------------------|
+| `version_or_name` | integer/string | Yes | The version or name of the migration. |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://primary.example.com/api/v4/admin/search/mirations/20230503064300"
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://primary.example.com/api/v4/admin/search/mirations/BackfillProjectPermissionsInBlobsUsingPermutations"
+```
+
+If successful, returns [`200`](rest/index.md#status-codes) and the following
+response attributes:
+
+| Attribute | Type | Description |
+|:------------------|:---------|:------------------------------------------------------|
+| `version` | integer | Version of the migration. |
+| `name` | string | Name of the migration. |
+| `started_at` | datetime | Start date for the migration. |
+| `completed_at` | datetime | Completion date for the migration. |
+| `completed` | boolean | If `true`, the migration is completed. |
+| `obsolete` | boolean | If `true`, the migration has been marked as obsolete. |
+| `migration_state` | object | Stored migration state. |
+
+Example response:
+
+```json
+{
+ "version": 20230503064300,
+ "name": "BackfillProjectPermissionsInBlobsUsingPermutations",
+ "started_at": "2023-05-03T16:04:44.074+00:00",
+ "completed_at": "2023-05-03T16:04:44.074+00:00",
+ "completed": true,
+ "obsolete": false,
+ "migration_state": {
+ "permutation_idx": 8,
+ "documents_remaining": 5,
+ "task_id": "I2_LXc-xQlOeu-KmjYpM8g:172820",
+ "documents_remaining_for_permutation": 0
+ }
+}
+```
diff --git a/doc/update/removals.md b/doc/update/removals.md
index 1673fb6bf49..e371e2f0fb0 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -804,6 +804,16 @@ The predefined CI/CD variables that start with `CI_BUILD_*` were deprecated in G
| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` |
| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` |
+### `POST /projects/:id/merge_requests/:merge_request_iid/approvals` removed
+
+<div class="deprecation-notes">
+- Announced in: GitLab <span class="milestone">12.3</span>
+- This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). Review the details carefully before upgrading.
+- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/353097).
+</div>
+
+The `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. To change the approvals required for a merge request via the API, use the `/approval_rules` endpoint described in [Create merge request level rule](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule).
+
### `POST ci/lint` API endpoint removed
<div class="deprecation-notes">
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 32454693d71..5d3bac4f895 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -19,6 +19,13 @@ Use epics:
- To track when the work for the group of issues is targeted to begin and end.
- To discuss and collaborate on feature ideas and scope at a high level.
+<div class="video-fallback">
+ See the video: <a href="https://www.youtube.com/watch?v=kdE-yb6Puuo">GitLab Epics - Setting up your Organization with GitLab</a>.
+</div>
+<figure class="video-container">
+ <iframe src="https://www.youtube-nocookie.com/embed/kdE-yb6Puuo" frameborder="0" allowfullscreen> </iframe>
+</figure>
+
## Relationships between epics and issues
The possible relationships between epics and issues are:
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 6c9a645d817..a43dd65ed74 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -22,6 +22,13 @@ For more information about using issues, see the GitLab blog post:
Issues are always associated with a specific project. If you have multiple
projects in a group, you can view all of the projects' issues at once.
+<div class="video-fallback">
+ See the video: <a href="https://www.youtube.com/watch?v=tTE6omrBBZI">Issues - Setting up your Organization with GitLab</a>.
+</div>
+<figure class="video-container">
+ <iframe src="https://www.youtube-nocookie.com/embed/tTE6omrBBZI" frameborder="0" allowfullscreen> </iframe>
+</figure>
+
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
To learn how the GitLab Strategic Marketing department uses GitLab issues with [labels](../labels.md) and
[issue boards](../issue_board.md), see the video on
diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb
index ef4b75ac3b4..2e06125963e 100644
--- a/spec/features/snippets/explore_spec.rb
+++ b/spec/features/snippets/explore_spec.rb
@@ -6,16 +6,15 @@ RSpec.describe 'Explore Snippets', feature_category: :source_code_management do
let!(:public_snippet) { create(:personal_snippet, :public) }
let!(:internal_snippet) { create(:personal_snippet, :internal) }
let!(:private_snippet) { create(:personal_snippet, :private) }
- let(:user) { nil }
-
- before do
- sign_in(user) if user
- visit explore_snippets_path
- end
context 'User' do
let(:user) { create(:user) }
+ before do
+ sign_in(user)
+ visit explore_snippets_path
+ end
+
it 'see snippets that are not private' do
expect(page).to have_content(public_snippet.title)
expect(page).to have_content(internal_snippet.title)
@@ -31,6 +30,11 @@ RSpec.describe 'Explore Snippets', feature_category: :source_code_management do
context 'External user' do
let(:user) { create(:user, :external) }
+ before do
+ sign_in(user)
+ visit explore_snippets_path
+ end
+
it 'see only public snippets' do
expect(page).to have_content(public_snippet.title)
expect(page).not_to have_content(internal_snippet.title)
@@ -55,6 +59,10 @@ RSpec.describe 'Explore Snippets', feature_category: :source_code_management do
end
context 'Not authenticated user' do
+ before do
+ visit explore_snippets_path
+ end
+
it 'see only public snippets' do
expect(page).to have_content(public_snippet.title)
expect(page).not_to have_content(internal_snippet.title)
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index e14f661a8bd..c8b1811cdeb 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -11,6 +11,7 @@ import getters from 'ee_else_ce/boards/stores/getters';
import BoardColumn from '~/boards/components/board_column.vue';
import BoardContent from '~/boards/components/board_content.vue';
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
+import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import { mockLists, mockListsById } from '../mock_data';
Vue.use(Vuex);
@@ -76,6 +77,9 @@ describe('BoardContent', () => {
});
};
+ const findBoardColumns = () => wrapper.findAllComponents(BoardColumn);
+ const findBoardAddNewColumn = () => wrapper.findComponent(BoardAddNewColumn);
+
describe('default', () => {
beforeEach(() => {
createComponent();
@@ -100,6 +104,10 @@ describe('BoardContent', () => {
expect(listEl.attributes('delay')).toBe('100');
expect(listEl.attributes('delayontouchonly')).toBe('true');
});
+
+ it('does not show the "add column" form', () => {
+ expect(findBoardAddNewColumn().exists()).toBe(false);
+ });
});
describe('when issuableType is not issue', () => {
@@ -155,4 +163,20 @@ describe('BoardContent', () => {
expect(eventHub.$on).toHaveBeenCalledWith('updateBoard', wrapper.vm.refetchLists);
});
});
+
+ describe('when "add column" form is visible', () => {
+ beforeEach(() => {
+ createComponent({ state: { addColumnForm: { visible: true } } });
+ });
+
+ it('shows the "add column" form', () => {
+ expect(findBoardAddNewColumn().exists()).toBe(true);
+ });
+
+ it('hides other columns on mobile viewports', () => {
+ findBoardColumns().wrappers.forEach((column) => {
+ expect(column.classes()).toEqual(['gl-display-none!', 'gl-sm-display-inline-block!']);
+ });
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/notes/system_note_spec.js b/spec/frontend/work_items/components/notes/system_note_spec.js
index fd5f373d076..03f1aa356ad 100644
--- a/spec/frontend/work_items/components/notes/system_note_spec.js
+++ b/spec/frontend/work_items/components/notes/system_note_spec.js
@@ -1,54 +1,32 @@
import { GlIcon } from '@gitlab/ui';
-import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
-import waitForPromises from 'helpers/wait_for_promises';
-import { renderGFM } from '~/behaviors/markdown/render_gfm';
+import MockAdapter from 'axios-mock-adapter';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import WorkItemSystemNote from '~/work_items/components/notes/system_note.vue';
-import NoteHeader from '~/notes/components/note_header.vue';
+import { workItemSystemNoteWithMetadata } from 'jest/work_items/mock_data';
import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
jest.mock('~/behaviors/markdown/render_gfm');
-describe('system note component', () => {
+describe('Work Items system note component', () => {
let wrapper;
- let props;
let mock;
- const findTimelineIcon = () => wrapper.findComponent(GlIcon);
- const findSystemNoteMessage = () => wrapper.findComponent(NoteHeader);
- const findOutdatedLineButton = () =>
- wrapper.findComponent('[data-testid="outdated-lines-change-btn"]');
- const findOutdatedLines = () => wrapper.findComponent('[data-testid="outdated-lines"]');
+ const createComponent = ({ note = workItemSystemNoteWithMetadata } = {}) => {
+ mock = new MockAdapter(axios);
- const createComponent = (propsData = {}) => {
wrapper = shallowMount(WorkItemSystemNote, {
- propsData,
- slots: {
- 'extra-controls':
- '<gl-button data-testid="outdated-lines-change-btn">Compare with last version</gl-button>',
+ propsData: {
+ note,
},
});
};
- beforeEach(() => {
- props = {
- note: {
- id: '1424',
- author: {
- id: 1,
- name: 'Root',
- username: 'root',
- state: 'active',
- avatarUrl: 'path',
- path: '/root',
- },
- bodyHtml: '<p dir="auto">closed</p>',
- systemNoteIconName: 'status_closed',
- createdAt: '2017-08-02T10:51:58.559Z',
- },
- };
+ const findTimelineIcon = () => wrapper.findComponent(GlIcon);
+ const findComparePreviousVersionButton = () => wrapper.find('[data-testid="compare-btn"]');
+ beforeEach(() => {
+ createComponent();
mock = new MockAdapter(axios);
});
@@ -57,56 +35,16 @@ describe('system note component', () => {
});
it('should render a list item with correct id', () => {
- createComponent(props);
-
- expect(wrapper.attributes('id')).toBe(`note_${props.note.id}`);
- });
-
- // Note: The test case below is to handle a use case related to vuex store but since this does not
- // have a vuex store , disabling it now will be fixing it in the next iteration
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('should render target class is note is target note', () => {
- createComponent(props);
-
- expect(wrapper.classes()).toContain('target');
+ expect(wrapper.attributes('id')).toBe(
+ `note_${getIdFromGraphQLId(workItemSystemNoteWithMetadata.id)}`,
+ );
});
it('should render svg icon', () => {
- createComponent(props);
-
expect(findTimelineIcon().exists()).toBe(true);
});
- // Redcarpet Markdown renderer wraps text in `<p>` tags
- // we need to strip them because they break layout of commit lists in system notes:
- // https://gitlab.com/gitlab-org/gitlab-foss/uploads/b07a10670919254f0220d3ff5c1aa110/jqzI.png
- it('removes wrapping paragraph from note HTML', () => {
- createComponent(props);
-
- expect(findSystemNoteMessage().html()).toContain('<span>closed</span>');
- });
-
- it('should renderGFM onMount', () => {
- createComponent(props);
-
- expect(renderGFM).toHaveBeenCalled();
- });
-
- // eslint-disable-next-line jest/no-disabled-tests
- it.skip('renders outdated code lines', async () => {
- mock
- .onGet('/outdated_line_change_path')
- .reply(HTTP_STATUS_OK, [
- { rich_text: 'console.log', type: 'new', line_code: '123', old_line: null, new_line: 1 },
- ]);
-
- createComponent({
- note: { ...props.note, outdated_line_change_path: '/outdated_line_change_path' },
- });
-
- await findOutdatedLineButton().vm.$emit('click');
- await waitForPromises();
-
- expect(findOutdatedLines().exists()).toBe(true);
+ it('should not show compare previous version for FOSS', () => {
+ expect(findComparePreviousVersionButton().exists()).toBe(false);
});
});
diff --git a/spec/frontend/work_items/components/notes/work_item_add_note_spec.js b/spec/frontend/work_items/components/notes/work_item_add_note_spec.js
index 739340f4936..e575b6bc097 100644
--- a/spec/frontend/work_items/components/notes/work_item_add_note_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_add_note_spec.js
@@ -225,7 +225,7 @@ describe('Work item add note', () => {
});
it('skips calling the work item query when missing workItemIid', async () => {
- await createComponent({ workItemIid: null, isEditing: false });
+ await createComponent({ workItemIid: '', isEditing: false });
expect(workItemResponseHandler).not.toHaveBeenCalled();
});
diff --git a/spec/frontend/work_items/components/work_item_labels_spec.js b/spec/frontend/work_items/components/work_item_labels_spec.js
index 554c9a4f7b8..6894aa236e3 100644
--- a/spec/frontend/work_items/components/work_item_labels_spec.js
+++ b/spec/frontend/work_items/components/work_item_labels_spec.js
@@ -266,7 +266,7 @@ describe('WorkItemLabels component', () => {
});
it('skips calling the work item query when missing workItemIid', async () => {
- createComponent({ workItemIid: null });
+ createComponent({ workItemIid: '' });
await waitForPromises();
expect(workItemQuerySuccess).not.toHaveBeenCalled();
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 05c6a21bb38..93f24cf2337 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -1879,6 +1879,10 @@ export const mockWorkItemNotesResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/36',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -1924,6 +1928,10 @@ export const mockWorkItemNotesResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/76',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -1968,6 +1976,10 @@ export const mockWorkItemNotesResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/71',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2073,6 +2085,10 @@ export const mockWorkItemNotesByIidResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/72',
+ descriptionVersion: null,
+ },
author: {
id: 'gid://gitlab/User/1',
avatarUrl:
@@ -2120,6 +2136,10 @@ export const mockWorkItemNotesByIidResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/76',
+ descriptionVersion: null,
+ },
author: {
id: 'gid://gitlab/User/1',
avatarUrl:
@@ -2168,6 +2188,10 @@ export const mockWorkItemNotesByIidResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/22',
+ descriptionVersion: null,
+ },
author: {
id: 'gid://gitlab/User/1',
avatarUrl:
@@ -2274,6 +2298,10 @@ export const mockMoreWorkItemNotesResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/16',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2321,6 +2349,10 @@ export const mockMoreWorkItemNotesResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/96',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2366,6 +2398,10 @@ export const mockMoreWorkItemNotesResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/56',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2430,6 +2466,7 @@ export const createWorkItemNoteResponse = {
webUrl: 'http://127.0.0.1:3000/root',
__typename: 'UserCore',
},
+ systemNoteMetadata: null,
userPermissions: {
adminNote: true,
awardEmoji: true,
@@ -2479,6 +2516,7 @@ export const mockWorkItemCommentNote = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: null,
author: {
avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
id: 'gid://gitlab/User/1',
@@ -2564,6 +2602,7 @@ export const mockWorkItemNotesResponseWithComments = {
webUrl: 'http://127.0.0.1:3000/root',
__typename: 'UserCore',
},
+ systemNoteMetadata: null,
userPermissions: {
adminNote: true,
awardEmoji: true,
@@ -2601,6 +2640,7 @@ export const mockWorkItemNotesResponseWithComments = {
webUrl: 'http://127.0.0.1:3000/root',
__typename: 'UserCore',
},
+ systemNoteMetadata: null,
userPermissions: {
adminNote: true,
awardEmoji: true,
@@ -2646,6 +2686,7 @@ export const mockWorkItemNotesResponseWithComments = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: null,
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2716,6 +2757,10 @@ export const workItemNotesCreateSubscriptionResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/65',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2739,6 +2784,10 @@ export const workItemNotesCreateSubscriptionResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/26',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2778,6 +2827,10 @@ export const workItemNotesUpdateSubscriptionResponse = {
repositionNote: true,
__typename: 'NotePermissions',
},
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/46',
+ descriptionVersion: null,
+ },
author: {
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
@@ -2801,3 +2854,303 @@ export const workItemNotesDeleteSubscriptionResponse = {
},
},
};
+
+export const workItemSystemNoteWithMetadata = {
+ id: 'gid://gitlab/Note/1651',
+ body: 'changed the description',
+ bodyHtml: '<p data-sourcepos="1:1-1:23" dir="auto">changed the description</p>',
+ system: true,
+ internal: false,
+ systemNoteIconName: 'pencil',
+ createdAt: '2023-05-05T07:19:37Z',
+ lastEditedAt: '2023-05-05T07:19:37Z',
+ url: 'https://gdk.test:3443/flightjs/Flight/-/work_items/46#note_1651',
+ lastEditedBy: null,
+ discussion: {
+ id: 'gid://gitlab/Discussion/7d4a46ea0525e2eeed451f7b718b0ebe73205374',
+ __typename: 'Discussion',
+ },
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'https://gdk.test:3443/root',
+ __typename: 'UserCore',
+ },
+ userPermissions: {
+ adminNote: false,
+ awardEmoji: true,
+ readNote: true,
+ createNote: true,
+ resolveNote: true,
+ repositionNote: false,
+ __typename: 'NotePermissions',
+ },
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/670',
+ descriptionVersion: {
+ id: 'gid://gitlab/DescriptionVersion/167',
+ description: '5th May 90 987',
+ diff: '<span class="idiff">5th May 90</span><span class="idiff addition"> 987</span>',
+ diffPath: '/flightjs/Flight/-/issues/46/descriptions/167/diff',
+ deletePath: '/flightjs/Flight/-/issues/46/descriptions/167',
+ canDelete: true,
+ deleted: false,
+ startVersionId: '',
+ __typename: 'DescriptionVersion',
+ },
+ __typename: 'SystemNoteMetadata',
+ },
+ __typename: 'Note',
+};
+
+export const workItemNotesWithSystemNotesWithChangedDescription = {
+ data: {
+ workspace: {
+ id: 'gid://gitlab/Project/4',
+ workItems: {
+ nodes: [
+ {
+ id: 'gid://gitlab/WorkItem/733',
+ iid: '79',
+ widgets: [
+ {
+ __typename: 'WorkItemWidgetAssignees',
+ },
+ {
+ __typename: 'WorkItemWidgetLabels',
+ },
+ {
+ __typename: 'WorkItemWidgetDescription',
+ },
+ {
+ __typename: 'WorkItemWidgetHierarchy',
+ },
+ {
+ __typename: 'WorkItemWidgetMilestone',
+ },
+ {
+ type: 'NOTES',
+ discussions: {
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: null,
+ endCursor: null,
+ __typename: 'PageInfo',
+ },
+ nodes: [
+ {
+ id: 'gid://gitlab/Discussion/aa72f4c2f3eef66afa6d79a805178801ce4bd89f',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Note/1687',
+ body: 'changed the description',
+ bodyHtml:
+ '<p data-sourcepos="1:1-1:23" dir="auto">changed the description</p>',
+ system: true,
+ internal: false,
+ systemNoteIconName: 'pencil',
+ createdAt: '2023-05-10T05:21:01Z',
+ lastEditedAt: '2023-05-10T05:21:01Z',
+ url: 'https://gdk.test:3443/gnuwget/Wget2/-/work_items/79#note_1687',
+ lastEditedBy: null,
+ discussion: {
+ id:
+ 'gid://gitlab/Discussion/aa72f4c2f3eef66afa6d79a805178801ce4bd89f',
+ __typename: 'Discussion',
+ },
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'https://gdk.test:3443/root',
+ __typename: 'UserCore',
+ },
+ userPermissions: {
+ adminNote: false,
+ awardEmoji: true,
+ readNote: true,
+ createNote: true,
+ resolveNote: true,
+ repositionNote: false,
+ __typename: 'NotePermissions',
+ },
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/703',
+ descriptionVersion: {
+ id: 'gid://gitlab/DescriptionVersion/198',
+ description: 'Desc1',
+ diff: '<span class="idiff addition">Desc1</span>',
+ diffPath: '/gnuwget/Wget2/-/issues/79/descriptions/198/diff',
+ deletePath: '/gnuwget/Wget2/-/issues/79/descriptions/198',
+ canDelete: true,
+ deleted: false,
+ __typename: 'DescriptionVersion',
+ },
+ __typename: 'SystemNoteMetadata',
+ },
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ {
+ id: 'gid://gitlab/Discussion/a7d3cf7bd72f7a98f802845f538af65cb11a02cc',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Note/1688',
+ body: 'changed the description',
+ bodyHtml:
+ '<p data-sourcepos="1:1-1:23" dir="auto">changed the description</p>',
+ system: true,
+ internal: false,
+ systemNoteIconName: 'pencil',
+ createdAt: '2023-05-10T05:21:05Z',
+ lastEditedAt: '2023-05-10T05:21:05Z',
+ url: 'https://gdk.test:3443/gnuwget/Wget2/-/work_items/79#note_1688',
+ lastEditedBy: null,
+ discussion: {
+ id:
+ 'gid://gitlab/Discussion/a7d3cf7bd72f7a98f802845f538af65cb11a02cc',
+ __typename: 'Discussion',
+ },
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'https://gdk.test:3443/root',
+ __typename: 'UserCore',
+ },
+ userPermissions: {
+ adminNote: false,
+ awardEmoji: true,
+ readNote: true,
+ createNote: true,
+ resolveNote: true,
+ repositionNote: false,
+ __typename: 'NotePermissions',
+ },
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/704',
+ descriptionVersion: {
+ id: 'gid://gitlab/DescriptionVersion/199',
+ description: 'Desc2',
+ diff:
+ '<span class="idiff">Desc</span><span class="idiff deletion">1</span><span class="idiff addition">2</span>',
+ diffPath: '/gnuwget/Wget2/-/issues/79/descriptions/199/diff',
+ deletePath: '/gnuwget/Wget2/-/issues/79/descriptions/199',
+ canDelete: true,
+ deleted: false,
+ __typename: 'DescriptionVersion',
+ },
+ __typename: 'SystemNoteMetadata',
+ },
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ {
+ id: 'gid://gitlab/Discussion/391eed1ee0a258cc966a51dde900424f3b51b95d',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Note/1689',
+ body: 'changed the description',
+ bodyHtml:
+ '<p data-sourcepos="1:1-1:23" dir="auto">changed the description</p>',
+ system: true,
+ internal: false,
+ systemNoteIconName: 'pencil',
+ createdAt: '2023-05-10T05:21:08Z',
+ lastEditedAt: '2023-05-10T05:21:08Z',
+ url: 'https://gdk.test:3443/gnuwget/Wget2/-/work_items/79#note_1689',
+ lastEditedBy: null,
+ discussion: {
+ id:
+ 'gid://gitlab/Discussion/391eed1ee0a258cc966a51dde900424f3b51b95d',
+ __typename: 'Discussion',
+ },
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'https://gdk.test:3443/root',
+ __typename: 'UserCore',
+ },
+ userPermissions: {
+ adminNote: false,
+ awardEmoji: true,
+ readNote: true,
+ createNote: true,
+ resolveNote: true,
+ repositionNote: false,
+ __typename: 'NotePermissions',
+ },
+ systemNoteMetadata: {
+ id: 'gid://gitlab/SystemNoteMetadata/705',
+ descriptionVersion: {
+ id: 'gid://gitlab/DescriptionVersion/200',
+ description: 'Desc3',
+ diff:
+ '<span class="idiff">Desc</span><span class="idiff deletion">2</span><span class="idiff addition">3</span>',
+ diffPath: '/gnuwget/Wget2/-/issues/79/descriptions/200/diff',
+ deletePath: '/gnuwget/Wget2/-/issues/79/descriptions/200',
+ canDelete: true,
+ deleted: false,
+ __typename: 'DescriptionVersion',
+ },
+ __typename: 'SystemNoteMetadata',
+ },
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ ],
+ __typename: 'DiscussionConnection',
+ },
+ __typename: 'WorkItemWidgetNotes',
+ },
+ {
+ __typename: 'WorkItemWidgetHealthStatus',
+ },
+ {
+ __typename: 'WorkItemWidgetProgress',
+ },
+ {
+ __typename: 'WorkItemWidgetNotifications',
+ },
+ {
+ __typename: 'WorkItemWidgetCurrentUserTodos',
+ },
+ {
+ __typename: 'WorkItemWidgetAwardEmoji',
+ },
+ ],
+ __typename: 'WorkItem',
+ },
+ ],
+ __typename: 'WorkItemConnection',
+ },
+ __typename: 'Project',
+ },
+ },
+};
diff --git a/spec/frontend/work_items/notes/collapse_utils_spec.js b/spec/frontend/work_items/notes/collapse_utils_spec.js
new file mode 100644
index 00000000000..c26ef891e9f
--- /dev/null
+++ b/spec/frontend/work_items/notes/collapse_utils_spec.js
@@ -0,0 +1,29 @@
+import {
+ isDescriptionSystemNote,
+ getTimeDifferenceInMinutes,
+} from '~/work_items/notes/collapse_utils';
+import { workItemSystemNoteWithMetadata } from '../mock_data';
+
+describe('Work items collapse utils', () => {
+ it('checks if a system note is of a description type', () => {
+ expect(isDescriptionSystemNote(workItemSystemNoteWithMetadata)).toEqual(true);
+ });
+
+ it('returns false when a system note is not a description type', () => {
+ expect(isDescriptionSystemNote({ ...workItemSystemNoteWithMetadata, system: false })).toEqual(
+ false,
+ );
+ });
+
+ it('gets the time difference between two notes', () => {
+ const anotherSystemNote = {
+ ...workItemSystemNoteWithMetadata,
+ createdAt: '2023-05-06T07:19:37Z',
+ };
+
+ // kept the dates 24 hours apart so 24 * 60 mins = 1440
+ expect(getTimeDifferenceInMinutes(workItemSystemNoteWithMetadata, anotherSystemNote)).toEqual(
+ 1440,
+ );
+ });
+});