diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-10 03:07:43 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-10 03:07:43 +0000 |
commit | 677227413ae31f5a02fd4c8e10f6ef50c228334f (patch) | |
tree | fbd07453cf1aaded55f081bb42fa043b3f3ffc2b | |
parent | 3f22924df411018ba665ecf72ab0768d61173477 (diff) | |
download | gitlab-ce-677227413ae31f5a02fd4c8e10f6ef50c228334f.tar.gz |
Add latest changes from gitlab-org/gitlab@master
21 files changed, 214 insertions, 61 deletions
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index e6f7a31e07b..f90d29c84b8 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -87,8 +87,8 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) { const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length; const shouldPad = processingFileCount >= 1; + addFileToForm(response.link.url, header.size); pasteText(response.link.markdown, shouldPad); - addFileToForm(response.link.url); }, error: (file, errorMessage = __('Attaching the file failed.'), xhr) => { // If 'error' event is fired by dropzone, the second parameter is error message. diff --git a/app/assets/javascripts/notes/components/attachments_warning.vue b/app/assets/javascripts/notes/components/attachments_warning.vue new file mode 100644 index 00000000000..aaa4b0d92b9 --- /dev/null +++ b/app/assets/javascripts/notes/components/attachments_warning.vue @@ -0,0 +1,18 @@ +<script> +import { COMMENT_FORM } from '../i18n'; + +export default { + i18n: COMMENT_FORM.attachmentMsg, + data() { + return { + message: this.$options.i18n, + }; + }, +}; +</script> + +<template> + <div class="issuable-note-warning" data-testid="attachment-warning"> + {{ message }} + </div> +</template> diff --git a/app/assets/javascripts/notes/components/comment_field_layout.vue b/app/assets/javascripts/notes/components/comment_field_layout.vue index 84bda1b0b5c..cc372520c70 100644 --- a/app/assets/javascripts/notes/components/comment_field_layout.vue +++ b/app/assets/javascripts/notes/components/comment_field_layout.vue @@ -1,14 +1,18 @@ <script> +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue'; import EmailParticipantsWarning from './email_participants_warning.vue'; +import AttachmentsWarning from './attachments_warning.vue'; const DEFAULT_NOTEABLE_TYPE = 'Issue'; export default { components: { + AttachmentsWarning, EmailParticipantsWarning, NoteableWarning, }, + mixins: [glFeatureFlagsMixin()], props: { noteableData: { type: Object, @@ -29,6 +33,11 @@ export default { required: false, default: false, }, + containsLink: { + type: Boolean, + required: false, + default: false, + }, }, computed: { isLocked() { @@ -46,6 +55,13 @@ export default { showEmailParticipantsWarning() { return this.emailParticipants.length && !this.isInternalNote; }, + showAttachmentWarning() { + return ( + this.glFeatures.serviceDeskNewNoteEmailNativeAttachments && + this.showEmailParticipantsWarning && + this.containsLink + ); + }, }, }; </script> @@ -68,6 +84,7 @@ export default { :confidential-noteable-docs-path="noteableData.confidential_issues_docs_path" /> <slot></slot> + <attachments-warning v-if="showAttachmentWarning" /> <email-participants-warning v-if="showEmailParticipantsWarning" class="gl-border-t-1 gl-border-t-solid gl-border-t-gray-100 gl-rounded-base gl-rounded-top-left-none! gl-rounded-top-right-none!" diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index c6e7117cf2e..4f7256d0b0e 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -28,6 +28,7 @@ import CommentTypeDropdown from './comment_type_dropdown.vue'; import DiscussionLockedWidget from './discussion_locked_widget.vue'; import NoteSignedOutWidget from './note_signed_out_widget.vue'; +const ATTACHMENT_REGEXP = /!?\[.*?\]\(\/uploads\/[0-9a-f]{32}\/.*?\)/; export default { name: 'CommentForm', i18n: COMMENT_FORM, @@ -176,6 +177,9 @@ export default { disableSubmitButton() { return this.note.length === 0 || this.isSubmitting; }, + containsLink() { + return ATTACHMENT_REGEXP.test(this.note); + }, }, mounted() { // jQuery is needed here because it is a custom event being dispatched with jQuery. @@ -356,6 +360,7 @@ export default { :noteable-data="getNoteableData" :is-internal-note="noteIsInternal" :noteable-type="noteableType" + :contains-link="containsLink" > <markdown-field ref="markdownField" diff --git a/app/assets/javascripts/notes/i18n.js b/app/assets/javascripts/notes/i18n.js index 9b5fd69f816..a758a55014a 100644 --- a/app/assets/javascripts/notes/i18n.js +++ b/app/assets/javascripts/notes/i18n.js @@ -45,4 +45,7 @@ export const COMMENT_FORM = { commentHelp: __('Add a general comment to this %{noteableDisplayName}.'), internalCommentHelp: __('Add a confidential internal note to this %{noteableDisplayName}.'), }, + attachmentMsg: s__( + 'Notes|Attachments are sent by email. Attachments over 10 MB are sent as links to your GitLab instance, and only accessible to project members.', + ), }; diff --git a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue index d07d0a7673f..7485f8282ee 100644 --- a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue +++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue @@ -1,6 +1,5 @@ <script> import { GlButton, GlFormCheckbox, GlKeysetPagination } from '@gitlab/ui'; -import { filter } from 'lodash'; import { __ } from '~/locale'; export default { @@ -52,24 +51,31 @@ export default { return this.pagination.hasPreviousPage || this.pagination.hasNextPage; }, disableDeleteButton() { - return this.isLoading || filter(this.selectedReferences).length === 0; + return this.isLoading || this.selectedItems.length === 0; }, selectedItems() { return this.items.filter(this.isSelected); }, - selectAll: { - get() { - return this.items.every(this.isSelected); - }, - set(value) { - this.items.forEach((item) => { - const id = item[this.idProperty]; - this.$set(this.selectedReferences, id, value); - }); - }, + disabled() { + return this.items.length === 0; + }, + checked() { + return this.items.every(this.isSelected); + }, + indeterminate() { + return !this.checked && this.items.some(this.isSelected); + }, + label() { + return this.checked ? __('Unselect all') : __('Select all'); }, }, methods: { + onChange(event) { + this.items.forEach((item) => { + const id = item[this.idProperty]; + this.$set(this.selectedReferences, id, event); + }); + }, selectItem(item) { const id = item[this.idProperty]; this.$set(this.selectedReferences, id, !this.selectedReferences[id]); @@ -80,7 +86,7 @@ export default { }, }, i18n: { - deleteSelected: __('Delete Selected'), + deleteSelected: __('Delete selected'), }, }; </script> @@ -91,9 +97,18 @@ export default { v-if="!hiddenDelete" class="gl-display-flex gl-justify-content-space-between gl-mb-3 gl-align-items-center" > - <gl-form-checkbox v-model="selectAll" class="gl-ml-2 gl-pt-2"> - <span class="gl-font-weight-bold">{{ title }}</span> - </gl-form-checkbox> + <div class="gl-display-flex gl-align-items-center"> + <gl-form-checkbox + class="gl-ml-2 gl-pt-2" + :aria-label="label" + :checked="checked" + :disabled="disabled" + :indeterminate="indeterminate" + @change="onChange" + /> + + <p class="gl-font-weight-bold gl-mb-0">{{ title }}</p> + </div> <gl-button :disabled="disableDeleteButton" diff --git a/app/assets/javascripts/vue_shared/components/registry/list_item.vue b/app/assets/javascripts/vue_shared/components/registry/list_item.vue index 66c8238a865..5d0ee6adffe 100644 --- a/app/assets/javascripts/vue_shared/components/registry/list_item.vue +++ b/app/assets/javascripts/vue_shared/components/registry/list_item.vue @@ -33,6 +33,7 @@ export default { 'gl-border-t-transparent': !this.first && !this.selected, 'gl-border-t-gray-100': this.first && !this.selected, 'gl-border-b-gray-100': !this.selected, + 'gl-border-t-transparent!': this.selected && !this.first, 'gl-bg-blue-50 gl-border-blue-200': this.selected, }; }, diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6f0b3ad1f35..3731257033d 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -46,6 +46,7 @@ class Projects::IssuesController < Projects::ApplicationController before_action do push_frontend_feature_flag(:preserve_unchanged_markdown, project) push_frontend_feature_flag(:content_editor_on_issues, project) + push_frontend_feature_flag(:service_desk_new_note_email_native_attachments, project) end before_action only: [:index, :show] do diff --git a/app/mailers/emails/service_desk.rb b/app/mailers/emails/service_desk.rb index ef0dd4fd2f9..835abacbed5 100644 --- a/app/mailers/emails/service_desk.rb +++ b/app/mailers/emails/service_desk.rb @@ -63,7 +63,7 @@ module Emails next unless template_body = template_content(email_type) options[:body] = template_body - options[:content_type] = 'text/html' + options[:content_type] = 'text/html' unless attachments.present? end end diff --git a/config/feature_flags/development/only_allow_merge_if_all_status_checks_passed.yml b/config/feature_flags/development/only_allow_merge_if_all_status_checks_passed.yml deleted file mode 100644 index b8d1b1f4175..00000000000 --- a/config/feature_flags/development/only_allow_merge_if_all_status_checks_passed.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: only_allow_merge_if_all_status_checks_passed -introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96765" -rollout_issue_url: "https://gitlab.com/gitlab-org/gitlab/-/issues/372340" -milestone: '15.5' -type: development -group: group::compliance -default_enabled: true diff --git a/db/post_migrate/20230201082038_drop_web_hook_calls_high_column.rb b/db/post_migrate/20230201082038_drop_web_hook_calls_high_column.rb new file mode 100644 index 00000000000..7f90b3293d8 --- /dev/null +++ b/db/post_migrate/20230201082038_drop_web_hook_calls_high_column.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class DropWebHookCallsHighColumn < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + with_lock_retries do + if column_exists?(:plan_limits, :web_hook_calls_high) # rubocop:disable Style/IfUnlessModifier + remove_column :plan_limits, :web_hook_calls_high + end + end + end + + def down + with_lock_retries do + unless column_exists?(:plan_limits, :web_hook_calls_high) + # rubocop:disable Migration/SchemaAdditionMethodsNoPost + add_column :plan_limits, :web_hook_calls_high, :integer, default: 0 + # rubocop:enable Migration/SchemaAdditionMethodsNoPost + end + end + end +end diff --git a/db/schema_migrations/20230201082038 b/db/schema_migrations/20230201082038 new file mode 100644 index 00000000000..b7e1ee666db --- /dev/null +++ b/db/schema_migrations/20230201082038 @@ -0,0 +1 @@ +0b735c9ccd267734fd4c4f6f15f6dfac67ec212786e4c31d75b73e2fae537617
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 067e8646819..0c9612dc425 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19745,9 +19745,7 @@ CREATE TABLE plan_limits ( enforcement_limit integer DEFAULT 0 NOT NULL, notification_limit integer DEFAULT 0 NOT NULL, dashboard_limit_enabled_at timestamp with time zone, - web_hook_calls_high integer DEFAULT 0, - web_hook_calls integer DEFAULT 0 NOT NULL, - CONSTRAINT check_0fa68f370e CHECK ((web_hook_calls_high IS NOT NULL)) + web_hook_calls integer DEFAULT 0 NOT NULL ); CREATE SEQUENCE plan_limits_id_seq diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index dfe4ac7446e..dd36d649227 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -1116,6 +1116,7 @@ Caches are: - Shared between pipelines and jobs. - By default, not shared between [protected](../../user/project/protected_branches.md) and unprotected branches. - Restored before [artifacts](#artifacts). +- Limited to a maximum of four [different caches](../caching/index.md#use-multiple-caches). You can [disable caching for specific jobs](../caching/index.md#disable-cache-for-specific-jobs), for example to override: diff --git a/doc/user/project/merge_requests/status_checks.md b/doc/user/project/merge_requests/status_checks.md index dadb1b392d9..c9f63ffd3da 100644 --- a/doc/user/project/merge_requests/status_checks.md +++ b/doc/user/project/merge_requests/status_checks.md @@ -31,11 +31,7 @@ see the [external status checks epic](https://gitlab.com/groups/gitlab-org/-/epi > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/369859) in GitLab 15.5 [with a flag](../../../administration/feature_flags.md) named `only_allow_merge_if_all_status_checks_passed`. Disabled by default. > - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/372340) in GitLab 15.8. -> - Enabled on self-managed in GitLab 15.9. - -FLAG: -On self-managed GitLab, this feature is available by default. To disable it per project or for your entire instance, ask an administrator to -[disable the feature flag](../../../administration/feature_flags.md) named `only_allow_merge_if_all_status_checks_passed`. On GitLab.com, this feature is available but can be configured by GitLab.com administrators only. +> - Enabled on self-managed and feature flag [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111492) in GitLab 15.9. By default, merge requests in projects can be merged even if external status checks fail. To block the merging of merge requests when external checks fail: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 39e9d8e1f11..02dcf7d0398 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13356,9 +13356,6 @@ msgstr "" msgid "Delete Key" msgstr "" -msgid "Delete Selected" -msgstr "" - msgid "Delete Value Stream" msgstr "" @@ -13434,6 +13431,9 @@ msgstr "" msgid "Delete row" msgstr "" +msgid "Delete selected" +msgstr "" + msgid "Delete self-monitoring project" msgstr "" @@ -28488,6 +28488,9 @@ msgstr "" msgid "Notes|Are you sure you want to cancel creating this comment?" msgstr "" +msgid "Notes|Attachments are sent by email. Attachments over 10 MB are sent as links to your GitLab instance, and only accessible to project members." +msgstr "" + msgid "Notes|Collapse replies" msgstr "" @@ -40887,9 +40890,6 @@ msgstr "" msgid "StatusCheck|Check for a status response in merge requests. %{link_start}Learn more%{link_end}." msgstr "" -msgid "StatusCheck|Check for a status response in merge requests. Failures do not block merges. %{link_start}Learn more%{link_end}." -msgstr "" - msgid "StatusCheck|Examples: QA, Security." msgstr "" diff --git a/spec/features/projects/issues/email_participants_spec.rb b/spec/features/projects/issues/email_participants_spec.rb index 753c4ea18c5..a902c8294d7 100644 --- a/spec/features/projects/issues/email_participants_spec.rb +++ b/spec/features/projects/issues/email_participants_spec.rb @@ -65,4 +65,18 @@ RSpec.describe 'viewing an issue', :js, feature_category: :service_desk do end end end + + context 'for feature flags' do + before do + sign_in(user) + end + + it 'pushes service_desk_new_note_email_native_attachments feature flag to frontend' do + stub_feature_flags(service_desk_new_note_email_native_attachments: true) + + visit project_issue_path(project, issue) + + expect(page).to have_pushed_frontend_feature_flags(serviceDeskNewNoteEmailNativeAttachments: true) + end + end end diff --git a/spec/frontend/notes/components/attachments_warning_spec.js b/spec/frontend/notes/components/attachments_warning_spec.js new file mode 100644 index 00000000000..0e99c26ed2b --- /dev/null +++ b/spec/frontend/notes/components/attachments_warning_spec.js @@ -0,0 +1,16 @@ +import { mount } from '@vue/test-utils'; +import AttachmentsWarning from '~/notes/components/attachments_warning.vue'; + +describe('Attachments Warning Component', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(AttachmentsWarning); + }); + + it('shows warning', () => { + const expected = + 'Attachments are sent by email. Attachments over 10 MB are sent as links to your GitLab instance, and only accessible to project members.'; + expect(wrapper.text()).toBe(expected); + }); +}); diff --git a/spec/frontend/notes/components/comment_field_layout_spec.js b/spec/frontend/notes/components/comment_field_layout_spec.js index 6662492fd81..93b54f95021 100644 --- a/spec/frontend/notes/components/comment_field_layout_spec.js +++ b/spec/frontend/notes/components/comment_field_layout_spec.js @@ -1,17 +1,13 @@ import { shallowMount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import CommentFieldLayout from '~/notes/components/comment_field_layout.vue'; +import AttachmentsWarning from '~/notes/components/attachments_warning.vue'; import EmailParticipantsWarning from '~/notes/components/email_participants_warning.vue'; import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue'; describe('Comment Field Layout Component', () => { let wrapper; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const LOCKED_DISCUSSION_DOCS_PATH = 'docs/locked/path'; const CONFIDENTIAL_ISSUES_DOCS_PATH = 'docs/confidential/path'; @@ -22,18 +18,32 @@ describe('Comment Field Layout Component', () => { confidential_issues_docs_path: CONFIDENTIAL_ISSUES_DOCS_PATH, }; + const commentFieldWithAttachmentData = { + noteableData: { + ...noteableDataMock, + issue_email_participants: [{ email: 'someone@gitlab.com' }, { email: 'another@gitlab.com' }], + }, + containsLink: true, + }; + const findIssuableNoteWarning = () => wrapper.findComponent(NoteableWarning); const findEmailParticipantsWarning = () => wrapper.findComponent(EmailParticipantsWarning); + const findAttachmentsWarning = () => wrapper.findComponent(AttachmentsWarning); const findErrorAlert = () => wrapper.findByTestId('comment-field-alert-container'); - const createWrapper = (props = {}, slots = {}) => { + const createWrapper = (props = {}, provide = {}) => { wrapper = extendedWrapper( shallowMount(CommentFieldLayout, { propsData: { noteableData: noteableDataMock, ...props, }, - slots, + provide: { + glFeatures: { + serviceDeskNewNoteEmailNativeAttachments: true, + }, + ...provide, + }, }), ); }; @@ -108,23 +118,25 @@ describe('Comment Field Layout Component', () => { expect(findEmailParticipantsWarning().exists()).toBe(false); }); + + it('does not show AttachmentWarning', () => { + createWrapper(); + + expect(findAttachmentsWarning().exists()).toBe(false); + }); }); describe('issue has email participants', () => { beforeEach(() => { - createWrapper({ - noteableData: { - ...noteableDataMock, - issue_email_participants: [ - { email: 'someone@gitlab.com' }, - { email: 'another@gitlab.com' }, - ], - }, - }); + createWrapper(commentFieldWithAttachmentData); }); it('shows EmailParticipantsWarning', () => { - expect(findEmailParticipantsWarning().isVisible()).toBe(true); + expect(findEmailParticipantsWarning().exists()).toBe(true); + }); + + it('shows AttachmentsWarning', () => { + expect(findAttachmentsWarning().isVisible()).toBe(true); }); it('sets EmailParticipantsWarning props', () => { @@ -148,4 +160,22 @@ describe('Comment Field Layout Component', () => { expect(findEmailParticipantsWarning().exists()).toBe(false); }); }); + + describe('serviceDeskNewNoteEmailNativeAttachments flag', () => { + it('shows warning message when flag is enabled', () => { + createWrapper(commentFieldWithAttachmentData, { + glFeatures: { serviceDeskNewNoteEmailNativeAttachments: true }, + }); + + expect(findAttachmentsWarning().exists()).toBe(true); + }); + + it('shows warning message when flag is disables', () => { + createWrapper(commentFieldWithAttachmentData, { + glFeatures: { serviceDeskNewNoteEmailNativeAttachments: false }, + }); + + expect(findAttachmentsWarning().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js b/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js index aaca58d21bb..2e2d5e26d33 100644 --- a/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js +++ b/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js @@ -54,6 +54,28 @@ describe('Registry List', () => { it('exists', () => { expect(findSelectAll().exists()).toBe(true); + expect(findSelectAll().attributes('aria-label')).toBe('Select all'); + expect(findSelectAll().attributes('disabled')).toBeUndefined(); + expect(findSelectAll().attributes('indeterminate')).toBeUndefined(); + }); + + it('sets disabled prop to true when items length is 0', () => { + mountComponent({ propsData: { ...defaultPropsData, items: [] } }); + + expect(findSelectAll().attributes('disabled')).toBe('true'); + }); + + it('when few are selected, sets indeterminate prop to true', async () => { + await findScopedSlotSelectButton(0).trigger('click'); + + expect(findSelectAll().attributes('indeterminate')).toBe('true'); + }); + + it('when all are selected, sets the right checkbox label', async () => { + findSelectAll().vm.$emit('change', true); + await nextTick(); + + expect(findSelectAll().attributes('aria-label')).toBe('Unselect all'); }); it('select and unselect all', async () => { @@ -63,7 +85,7 @@ describe('Registry List', () => { }); // simulate selection - findSelectAll().vm.$emit('input', true); + findSelectAll().vm.$emit('change', true); await nextTick(); // all rows selected @@ -72,12 +94,12 @@ describe('Registry List', () => { }); // simulate de-selection - findSelectAll().vm.$emit('input', ''); + findSelectAll().vm.$emit('change', false); await nextTick(); // no row is not selected items.forEach((item, index) => { - expect(findScopedSlotIsSelectedValue(index).text()).toBe(''); + expect(findScopedSlotIsSelectedValue(index).text()).toBe('false'); }); }); }); diff --git a/spec/mailers/emails/service_desk_spec.rb b/spec/mailers/emails/service_desk_spec.rb index e753bf2c76c..1f55aabc535 100644 --- a/spec/mailers/emails/service_desk_spec.rb +++ b/spec/mailers/emails/service_desk_spec.rb @@ -70,7 +70,7 @@ RSpec.describe Emails::ServiceDesk do is_expected.to have_referable_subject(issue, include_project: false, reply: reply_in_subject) is_expected.to have_body_text(expected_body) expect(subject.attachments.count).to eq(attachments_count.to_i) - expect(subject.content_type).to include('text/html') + expect(subject.content_type).to include(attachments_count.to_i > 0 ? 'multipart/mixed' : 'text/html') end end end |