diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 09:10:09 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 09:10:09 +0000 |
commit | eeb25534bae1021f5b7940138ee56dea8fc79949 (patch) | |
tree | 26cf3d6a4ac582ed3d0d3a20b82a200da580f1e3 | |
parent | 75621c94b5dbe233edd72c3d8cc602fed25e84d2 (diff) | |
download | gitlab-ce-eeb25534bae1021f5b7940138ee56dea8fc79949.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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> · <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, + ); + }); +}); |