From bca3fb69e1e43c345ccac26a08ae1d374920020d Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 20 Aug 2021 18:12:04 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../Navigation - Left Sidebar Proposals.md | 15 ++ .../Security developer workflow.md | 1 + .../merge_request_templates/Documentation.md | 1 + .../Security Release.md | 2 +- .../components/wrappers/table_cell.vue | 127 ++++++++++++++ .../content_editor/extensions/table_cell.js | 6 + app/assets/javascripts/environments/index.js | 2 +- .../ide/components/repo_editor.vue | 6 +- .../edit/components/jira_trigger_fields.vue | 4 +- .../pipelines_list/pipeline_multi_actions.vue | 1 + .../sidebar_participants_widget.vue | 6 + .../stylesheets/framework/typography.scss | 15 +- app/assets/stylesheets/utilities.scss | 7 +- app/models/commit_status.rb | 6 +- app/models/namespace.rb | 5 - app/services/projects/transfer_service.rb | 23 ++- danger/metadata/Dangerfile | 8 +- .../geo/replication/datatypes.md | 1 + .../runners/build_cloud/macos_build_cloud.md | 2 +- doc/user/project/settings/import_export.md | 2 + locale/gitlab.pot | 32 +++- package.json | 1 + .../components/wrappers/table_cell_spec.js | 164 ++++++++++++++++++ .../ide/components/repo_editor_spec.js | 35 ++-- .../sidebar_participants_widget_spec.js | 10 +- .../projects/transfer_service_spec.rb | 33 +++- 26 files changed, 467 insertions(+), 48 deletions(-) create mode 100644 .gitlab/issue_templates/Navigation - Left Sidebar Proposals.md create mode 100644 app/assets/javascripts/content_editor/components/wrappers/table_cell.vue create mode 100644 spec/frontend/content_editor/components/wrappers/table_cell_spec.js diff --git a/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md b/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md new file mode 100644 index 00000000000..57d6d12267c --- /dev/null +++ b/.gitlab/issue_templates/Navigation - Left Sidebar Proposals.md @@ -0,0 +1,15 @@ + + +### Proposal + + + +### Checklist + +- [ ] If your proposal includes changes to the top-level menu items within the left sidebar, engage the [Foundations Product Design Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI will work with UX partners in product design, research, and technical writing, as applicable. +- [ ] Follow the [product development workflow](https://about.gitlab.com/handbook/product-development-flow/#validation-phase-2-problem-validation) validation process to ensure you are solving a well understood problem and that the proposed change is understandable and non-disruptive to users. Navigation-specific research is strongly encouraged. +- [ ] Engage the [Editor](https://about.gitlab.com/handbook/engineering/development/dev/create-editor/) team to ensure your proposal is in alignment with holistic changes happening to the left side bar. +- [ ] Consider whether you need to communicate the change somehow, or if you will have an interim period in the UI where your nav item will live in more than one place. +- [ ] Once implemented, update this [navigation map in Mural](https://app.mural.co/t/gitlab2474/m/gitlab2474/1589571490215/261462d0beb3043979374623710d3f2d6cfec1cb) with your navigation change. + +/label ~UX ~"UI text" ~"documentation" ~"documentation" ~"Category:Navigation & Settings" ~"Category:Foundations" ~navigation diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index 51e8ec378b2..7f2c54f4f49 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -29,6 +29,7 @@ After your merge request has been approved according to our [approval guidelines ## Backports - [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches + * The 3 stable branches correspond to the versions in the title of the Security Release Tracking Issue. * At this point, it might be easy to squash the commits from the MR into one * You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation] - [ ] Create each MR targeting the stable branch `X-Y-stable`, using the [Security Release merge request template]. diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index e97ae9a0c43..63b50db08ea 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -18,6 +18,7 @@ ## Author's checklist +- Consider taking [the GitLab Technical Writing Fundamentals course](https://gitlab.edcast.com/pathways/ECL-02528ee2-c334-4e16-abf3-e9d8b8260de4) - [ ] Follow the: - [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/). - [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide/). diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md index 33c0a5b98a8..7684546b91c 100644 --- a/.gitlab/merge_request_templates/Security Release.md +++ b/.gitlab/merge_request_templates/Security Release.md @@ -14,7 +14,6 @@ See [the general developer security release guidelines](https://gitlab.com/gitla - [ ] **On "Related issues" section, write down the [GitLab Security] issue it belongs to (i.e. `Related to `).** - [ ] Merge request targets `master`, or a versioned stable branch (`X-Y-stable-ee`). -- [ ] Milestone is set for the version this merge request applies to. A closed milestone can be assigned via [quick actions]. - [ ] Title of this merge request is the same as for all backports. - [ ] A [CHANGELOG entry] has been included, with `Changelog` trailer set to `security`. - [ ] For the MR targeting `master`: @@ -24,6 +23,7 @@ See [the general developer security release guidelines](https://gitlab.com/gitla - Please see the security release [Code reviews and Approvals](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#code-reviews-and-approvals) documentation for details on which AppSec team member to ping for approval. - Trigger the [`package-and-qa` build]. The docker image generated will be used by the AppSec engineer to validate the security vulnerability has been remediated. - [ ] For a backport MR targeting a versioned stable branch (`X-Y-stable-ee`) + - [ ] Milestone is set to the version this backport applies to. A closed milestone can be assigned via [quick actions]. - [ ] Ensure it's approved by a maintainer. **Note:** Reviewer/maintainer should not be a Release Manager diff --git a/app/assets/javascripts/content_editor/components/wrappers/table_cell.vue b/app/assets/javascripts/content_editor/components/wrappers/table_cell.vue new file mode 100644 index 00000000000..5cc88ef357a --- /dev/null +++ b/app/assets/javascripts/content_editor/components/wrappers/table_cell.vue @@ -0,0 +1,127 @@ + + diff --git a/app/assets/javascripts/content_editor/extensions/table_cell.js b/app/assets/javascripts/content_editor/extensions/table_cell.js index b77e6a1c8bb..3008ac0afdb 100644 --- a/app/assets/javascripts/content_editor/extensions/table_cell.js +++ b/app/assets/javascripts/content_editor/extensions/table_cell.js @@ -1,6 +1,12 @@ import { TableCell } from '@tiptap/extension-table-cell'; +import { VueNodeViewRenderer } from '@tiptap/vue-2'; +import TableCellWrapper from '../components/wrappers/table_cell.vue'; import { isBlockTablesFeatureEnabled } from '../services/feature_flags'; export default TableCell.extend({ content: isBlockTablesFeatureEnabled() ? 'block+' : 'inline*', + + addNodeView() { + return VueNodeViewRenderer(TableCellWrapper); + }, }); diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js index b99872f7a6c..f1f8b4c8bc8 100644 --- a/app/assets/javascripts/environments/index.js +++ b/app/assets/javascripts/environments/index.js @@ -9,7 +9,7 @@ Vue.use(Translate); Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient({}, { assumeImmutableResults: true }), }); export default () => { diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index bf5ec849bc5..2f990280367 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -3,6 +3,7 @@ import { debounce } from 'lodash'; import { mapState, mapGetters, mapActions } from 'vuex'; import { EDITOR_TYPE_DIFF, + EDITOR_TYPE_CODE, EDITOR_CODE_INSTANCE_FN, EDITOR_DIFF_INSTANCE_FN, } from '~/editor/constants'; @@ -311,7 +312,10 @@ export default { }), ); - if (this.fileType === MARKDOWN_FILE_TYPE) { + if ( + this.fileType === MARKDOWN_FILE_TYPE && + this.editor?.getEditorType() === EDITOR_TYPE_CODE + ) { import('~/editor/extensions/source_editor_markdown_ext') .then(({ EditorMarkdownExtension: MarkdownExtension } = {}) => { this.editor.use( diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue index 11e9b25f9a3..1cc5a185f03 100644 --- a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue @@ -37,7 +37,7 @@ const issueTransitionOptions = [ help: s__( 'JiraService|Automatically transitions Jira issues to the "Done" category. %{linkStart}Learn more%{linkEnd}', ), - link: helpPagePath('integration/jira/index.html', { + link: helpPagePath('integration/jira/issues.html', { anchor: 'automatic-issue-transitions', }), }, @@ -47,7 +47,7 @@ const issueTransitionOptions = [ help: s__( 'JiraService|Set a custom final state by using transition IDs. %{linkStart}Learn about transition IDs%{linkEnd}', ), - link: helpPagePath('integration/jira/index.html', { + link: helpPagePath('integration/jira/issues.html', { anchor: 'custom-issue-transitions', }), }, diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue index 40ee071f1f5..635511eb0b4 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue @@ -110,6 +110,7 @@ export default { :href="artifact.path" rel="nofollow" download + class="gl-word-break-word" data-testid="artifact-item" > diff --git a/app/assets/javascripts/sidebar/components/participants/sidebar_participants_widget.vue b/app/assets/javascripts/sidebar/components/participants/sidebar_participants_widget.vue index 39f72b251c7..a09138a708b 100644 --- a/app/assets/javascripts/sidebar/components/participants/sidebar_participants_widget.vue +++ b/app/assets/javascripts/sidebar/components/participants/sidebar_participants_widget.vue @@ -56,6 +56,11 @@ export default { return this.$apollo.queries.participants.loading; }, }, + methods: { + toggleSidebar() { + this.$emit('toggleSidebar'); + }, + }, }; @@ -66,5 +71,6 @@ export default { :number-of-less-participants="7" :lazy="false" class="block participants" + @toggleSidebar="toggleSidebar" /> diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 603b05efe10..00a471497e6 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -549,17 +549,12 @@ margin: 0; font-size: $gl-font-size-small; } + } - ul.dropdown-menu { - margin-top: 4px; - margin-bottom: 24px; - padding: 8px 0; - - li { - margin: 0; - padding: 0 1px; - } - } + .gl-new-dropdown-item { + margin: 0; + padding: 0; + line-height: 1rem; } /* AsciiDoc(tor) built-in alignment roles */ diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index ccad503c1ed..bf271f4d355 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -245,11 +245,16 @@ $gl-line-height-42: px-to-rem(42px); width: $grid-size * 28; } -// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1491 +// Will be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2347 is merged .gl-min-w-8 { min-width: $gl-spacing-scale-8; } +// Will be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2347 is merged +.gl-min-w-10 { + min-width: $gl-spacing-scale-10; +} + // Will both be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1526 .gl-opacity-6 { opacity: 0.6; diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index b34d64de101..8ff5a6f38e2 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -63,8 +63,12 @@ class CommitStatus < Ci::ApplicationRecord where('(ci_builds.created_at BETWEEN ? AND ?) AND (ci_builds.updated_at BETWEEN ? AND ?)', lookback, timeout, lookback, timeout) } + # The scope applies `pluck` to split the queries. Use with care. scope :for_project_paths, -> (paths) do - where(project: Project.where_full_path_in(Array(paths))) + # Pluck is used to split this query. Splitting the query is required for database decomposition for `ci_*` tables. + # https://docs.gitlab.com/ee/development/database/transaction_guidelines.html#database-decomposition-and-sharding + project_ids = Project.where_full_path_in(Array(paths)).pluck(:id) + where(project: project_ids) end scope :with_preloads, -> do diff --git a/app/models/namespace.rb b/app/models/namespace.rb index afa41085820..261639a4ec1 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -274,11 +274,6 @@ class Namespace < ApplicationRecord projects.with_shared_runners.any? end - # Internal Gitlab owned namespaces only (example: gitlab-org) - def unlimited_minutes? - shared_runners_minutes_limit == 0 - end - def user_ids_for_project_authorizations [owner_id] end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 074550e104d..27376173f07 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -20,8 +20,16 @@ module Projects raise TransferError, s_('TransferProject|Please select a new namespace for your project.') end - unless allowed_transfer?(current_user, project) - raise TransferError, s_('TransferProject|Transfer failed, please contact an admin.') + if @new_namespace.id == project.namespace_id + raise TransferError, s_('TransferProject|Project is already in this namespace.') + end + + unless allowed_transfer_project?(current_user, project) + raise TransferError, s_("TransferProject|You don't have permission to transfer this project.") + end + + unless allowed_to_transfer_to_namespace?(current_user, @new_namespace) + raise TransferError, s_("TransferProject|You don't have permission to transfer projects into that namespace.") end transfer(project) @@ -121,11 +129,12 @@ module Projects Milestones::TransferService.new(current_user, group, project).execute end - def allowed_transfer?(current_user, project) - @new_namespace && - can?(current_user, :change_namespace, project) && - @new_namespace.id != project.namespace_id && - current_user.can?(:transfer_projects, @new_namespace) + def allowed_transfer_project?(current_user, project) + current_user.can?(:change_namespace, project) + end + + def allowed_to_transfer_to_namespace?(current_user, namespace) + current_user.can?(:transfer_projects, namespace) end def update_namespace_and_visibility(to_namespace) diff --git a/danger/metadata/Dangerfile b/danger/metadata/Dangerfile index d2e85109d63..27dda687f6a 100644 --- a/danger/metadata/Dangerfile +++ b/danger/metadata/Dangerfile @@ -2,6 +2,8 @@ # rubocop:disable Style/SignalException +DEFAULT_BRANCH = 'master' + THROUGHPUT_LABELS = [ 'Community contribution', 'security', @@ -29,12 +31,12 @@ end has_milestone = !gitlab.mr_json["milestone"].nil? -unless has_milestone +unless has_milestone || (helper.security_mr? && gitlab.branch_for_base == DEFAULT_BRANCH) warn "This merge request does not refer to an existing milestone.", sticky: false end has_pick_into_stable_label = gitlab.mr_labels.find { |label| label.start_with?('Pick into') } -if gitlab.branch_for_base != "master" && !has_pick_into_stable_label && !helper.security_mr? - warn "Most of the time, merge requests should target `master`. Otherwise, please set the relevant `Pick into X.Y` label." +if gitlab.branch_for_base != DEFAULT_BRANCH && !has_pick_into_stable_label && !helper.security_mr? + warn "Most of the time, merge requests should target `#{DEFAULT_BRANCH}`. Otherwise, please set the relevant `Pick into X.Y` label." end diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md index a696e5410e5..27a63493131 100644 --- a/doc/administration/geo/replication/datatypes.md +++ b/doc/administration/geo/replication/datatypes.md @@ -198,6 +198,7 @@ successfully, you must replicate their data using some other means. |[Package Registry for PyPI](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. | |[Package Registry for Composer](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. | |[Package Registry for generic packages](../../../user/packages/generic_packages/index.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. | +|[Package Registry for Helm charts](../../../user/packages/helm_repository/index.md) | **Yes** (14.1) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (14.1) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind [feature flag](../../feature_flags.md) `geo_package_file_replication`, enabled by default. | |[Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.12) | Via Object Storage provider if supported. Native Geo support (Beta). | Replication is behind the feature flag `geo_terraform_state_version_replication`, enabled by default. Verification was behind the feature flag `geo_terraform_state_version_verification`, which was removed in 14.0| |[External merge request diffs](../../merge_request_diffs.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Replication is behind the feature flag `geo_merge_request_diff_replication`, enabled by default. Verification is under development, behind the feature flag `geo_merge_request_diff_verification`, introduced in 14.0.| |[Versioned snippets](../../../user/snippets.md#versioned-snippets) | [**Yes** (13.7)](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [**Yes** (14.2)](https://gitlab.com/groups/gitlab-org/-/epics/2810) | No | Verification was implemented behind the feature flag `geo_snippet_repository_verification` in 13.11, and the feature flag was removed in 14.2. | diff --git a/doc/ci/runners/build_cloud/macos_build_cloud.md b/doc/ci/runners/build_cloud/macos_build_cloud.md index 1400c7e08db..55f334a4c98 100644 --- a/doc/ci/runners/build_cloud/macos_build_cloud.md +++ b/doc/ci/runners/build_cloud/macos_build_cloud.md @@ -16,7 +16,7 @@ and shouldn't be relied upon for mission-critical production jobs. ## Quickstart -To start using Build Cloud for macOS Beta, you must submit an access request issue. After your +To start using Build Cloud for macOS Beta, you must submit an access request [issue](https://gitlab.com/gitlab-com/macos-buildcloud-runners-beta/-/issues/new?issuable_template=beta_access_request). After your access has been granted and your build environment configured, you must configure your `.gitlab-ci.yml` pipeline file: diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index a0718e875d7..82dcd4a1a13 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -45,6 +45,8 @@ Note the following: a maintainer or administrator role in the group where the exported project lives. - Project members with the [Owner role](../../permissions.md) are imported as Maintainers. - Imported users can be mapped by their primary email on self-managed instances, if an administrative user (not an owner) does the import. + Additionally, the user must be an existing member of the namespace, or the user can be added as a +member of the project for contributions to be mapped. Otherwise, a supplementary comment is left to mention that the original author and the MRs, notes, or issues are owned by the importer. - For project migration imports performed over GitLab.com Groups, preserving author information is diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 94ce205e36f..690790aa5da 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10780,6 +10780,9 @@ msgstr "" msgid "Delete badge" msgstr "" +msgid "Delete column" +msgstr "" + msgid "Delete comment" msgstr "" @@ -10810,6 +10813,9 @@ msgstr "" msgid "Delete project. Are you ABSOLUTELY SURE?" msgstr "" +msgid "Delete row" +msgstr "" + msgid "Delete self monitoring project" msgstr "" @@ -10828,6 +10834,9 @@ msgstr "" msgid "Delete subscription" msgstr "" +msgid "Delete table" +msgstr "" + msgid "Delete this attachment" msgstr "" @@ -12128,6 +12137,9 @@ msgstr "" msgid "Edit sidebar" msgstr "" +msgid "Edit table" +msgstr "" + msgid "Edit this file only." msgstr "" @@ -17811,6 +17823,12 @@ msgstr "" msgid "Insert code" msgstr "" +msgid "Insert column after" +msgstr "" + +msgid "Insert column before" +msgstr "" + msgid "Insert image" msgstr "" @@ -17820,6 +17838,12 @@ msgstr "" msgid "Insert link" msgstr "" +msgid "Insert row after" +msgstr "" + +msgid "Insert row before" +msgstr "" + msgid "Insert suggestion" msgstr "" @@ -35180,13 +35204,19 @@ msgstr "" msgid "TransferProject|Project cannot be transferred, because tags are present in its container registry" msgstr "" +msgid "TransferProject|Project is already in this namespace." +msgstr "" + msgid "TransferProject|Project with same name or path in target namespace already exists" msgstr "" msgid "TransferProject|Root namespace can't be updated if project has NPM packages" msgstr "" -msgid "TransferProject|Transfer failed, please contact an admin." +msgid "TransferProject|You don't have permission to transfer projects into that namespace." +msgstr "" + +msgid "TransferProject|You don't have permission to transfer this project." msgstr "" msgid "Tree view" diff --git a/package.json b/package.json index badee93d163..d8cc2d554a7 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,7 @@ "prosemirror-markdown": "^1.5.1", "prosemirror-model": "^1.13.3", "prosemirror-state": "^1.3.4", + "prosemirror-tables": "^1.1.1", "raphael": "^2.2.7", "raw-loader": "^4.0.2", "scrollparent": "^2.0.1", diff --git a/spec/frontend/content_editor/components/wrappers/table_cell_spec.js b/spec/frontend/content_editor/components/wrappers/table_cell_spec.js new file mode 100644 index 00000000000..274c2c2f3de --- /dev/null +++ b/spec/frontend/content_editor/components/wrappers/table_cell_spec.js @@ -0,0 +1,164 @@ +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { NodeViewWrapper } from '@tiptap/vue-2'; +import { selectedRect as getSelectedRect } from 'prosemirror-tables'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import TableCellWrapper from '~/content_editor/components/wrappers/table_cell.vue'; +import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../../test_utils'; + +jest.mock('prosemirror-tables'); + +describe('content/components/wrappers/table_cell', () => { + let wrapper; + let editor; + let getPos; + + const createWrapper = async () => { + wrapper = shallowMountExtended(TableCellWrapper, { + propsData: { + editor, + getPos, + }, + }); + }; + + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownItemWithLabel = (name) => + wrapper + .findAllComponents(GlDropdownItem) + .filter((dropdownItem) => dropdownItem.text().includes(name)) + .at(0); + const findDropdownItemWithLabelExists = (name) => + wrapper + .findAllComponents(GlDropdownItem) + .filter((dropdownItem) => dropdownItem.text().includes(name)).length > 0; + const setCurrentPositionInCell = () => { + const { $cursor } = editor.state.selection; + + getPos.mockReturnValue($cursor.pos - $cursor.parentOffset - 1); + }; + const mockDropdownHide = () => { + /* + * TODO: Replace this method with using the scoped hide function + * provided by BootstrapVue https://bootstrap-vue.org/docs/components/dropdown. + * GitLab UI is not exposing it in the default scope + */ + findDropdown().vm.hide = jest.fn(); + }; + + beforeEach(() => { + getPos = jest.fn(); + editor = createTestEditor({}); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders a td node-view-wrapper with relative position', () => { + createWrapper(); + expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('gl-relative'); + expect(wrapper.findComponent(NodeViewWrapper).props().as).toBe('td'); + }); + + it('displays dropdown when selection cursor is on the cell', async () => { + setCurrentPositionInCell(); + createWrapper(); + + await wrapper.vm.$nextTick(); + + expect(findDropdown().props()).toMatchObject({ + category: 'tertiary', + icon: 'chevron-down', + size: 'small', + split: false, + }); + expect(findDropdown().attributes()).toMatchObject({ + boundary: 'viewport', + 'no-caret': '', + }); + }); + + it('does not display dropdown when selection cursor is not on the cell', async () => { + createWrapper(); + + await wrapper.vm.$nextTick(); + + expect(findDropdown().exists()).toBe(false); + }); + + describe('when dropdown is visible', () => { + beforeEach(async () => { + setCurrentPositionInCell(); + getSelectedRect.mockReturnValue({ + map: { + height: 1, + width: 1, + }, + }); + + createWrapper(); + await wrapper.vm.$nextTick(); + + mockDropdownHide(); + }); + + it.each` + dropdownItemLabel | commandName + ${'Insert column before'} | ${'addColumnBefore'} + ${'Insert column after'} | ${'addColumnAfter'} + ${'Insert row before'} | ${'addRowBefore'} + ${'Insert row after'} | ${'addRowAfter'} + ${'Delete table'} | ${'deleteTable'} + `( + 'executes $commandName when $dropdownItemLabel button is clicked', + ({ commandName, dropdownItemLabel }) => { + const mocks = mockChainedCommands(editor, [commandName, 'run']); + + findDropdownItemWithLabel(dropdownItemLabel).vm.$emit('click'); + + expect(mocks[commandName]).toHaveBeenCalled(); + }, + ); + + it('does not allow deleting rows and columns', async () => { + expect(findDropdownItemWithLabelExists('Delete row')).toBe(false); + expect(findDropdownItemWithLabelExists('Delete column')).toBe(false); + }); + + it('allows deleting rows when there are more than 2 rows in the table', async () => { + const mocks = mockChainedCommands(editor, ['deleteRow', 'run']); + + getSelectedRect.mockReturnValue({ + map: { + height: 3, + }, + }); + + emitEditorEvent({ tiptapEditor: editor, event: 'selectionUpdate' }); + + await wrapper.vm.$nextTick(); + + findDropdownItemWithLabel('Delete row').vm.$emit('click'); + + expect(mocks.deleteRow).toHaveBeenCalled(); + }); + + it('allows deleting columns when there are more than 1 column in the table', async () => { + const mocks = mockChainedCommands(editor, ['deleteColumn', 'run']); + + getSelectedRect.mockReturnValue({ + map: { + width: 2, + }, + }); + + emitEditorEvent({ tiptapEditor: editor, event: 'selectionUpdate' }); + + await wrapper.vm.$nextTick(); + + findDropdownItemWithLabel('Delete column').vm.$emit('click'); + + expect(mocks.deleteColumn).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js index 3f722c24dbb..b2254de706c 100644 --- a/spec/frontend/ide/components/repo_editor_spec.js +++ b/spec/frontend/ide/components/repo_editor_spec.js @@ -166,11 +166,6 @@ describe('RepoEditor', () => { expect(tabs).toHaveLength(1); expect(tabs.at(0).text()).toBe('Edit'); }); - - it('does not get markdown extension by default', async () => { - await createComponent(); - expect(vm.editor.projectPath).toBeUndefined(); - }); }); describe('when file is markdown', () => { @@ -218,11 +213,6 @@ describe('RepoEditor', () => { }); expect(findTabs()).toHaveLength(0); }); - - it('uses the markdown extension and sets it up correctly', async () => { - await createComponent({ activeFile }); - expect(vm.editor.projectPath).toBe(vm.currentProjectId); - }); }); describe('when file is binary and not raw', () => { @@ -271,6 +261,31 @@ describe('RepoEditor', () => { expect(vm.editor[fn]).toBe(EditorWebIdeExtension.prototype[fn]); }); }); + + it.each` + prefix | activeFile | viewer | shouldHaveMarkdownExtension + ${'Should not'} | ${createActiveFile()} | ${viewerTypes.edit} | ${false} + ${'Should'} | ${dummyFile.markdown} | ${viewerTypes.edit} | ${true} + ${'Should not'} | ${dummyFile.empty} | ${viewerTypes.edit} | ${false} + ${'Should not'} | ${createActiveFile()} | ${viewerTypes.diff} | ${false} + ${'Should not'} | ${dummyFile.markdown} | ${viewerTypes.diff} | ${false} + ${'Should not'} | ${dummyFile.empty} | ${viewerTypes.diff} | ${false} + ${'Should not'} | ${createActiveFile()} | ${viewerTypes.mr} | ${false} + ${'Should not'} | ${dummyFile.markdown} | ${viewerTypes.mr} | ${false} + ${'Should not'} | ${dummyFile.empty} | ${viewerTypes.mr} | ${false} + `( + '$prefix install markdown extension for $activeFile.name in $viewer viewer', + async ({ activeFile, viewer, shouldHaveMarkdownExtension } = {}) => { + await createComponent({ state: { viewer }, activeFile }); + if (shouldHaveMarkdownExtension) { + expect(vm.editor.projectPath).toBe(vm.currentProjectId); + expect(vm.editor.togglePreview).toBeDefined(); + } else { + expect(vm.editor.projectPath).toBeUndefined(); + expect(vm.editor.togglePreview).toBeUndefined(); + } + }, + ); }); describe('setupEditor', () => { diff --git a/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js b/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js index 57b9a10b23e..859e63b3df6 100644 --- a/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js +++ b/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -45,6 +45,14 @@ describe('Sidebar Participants Widget', () => { expect(findParticipants().props('loading')).toBe(true); }); + it('emits toggleSidebar event when participants child component emits toggleSidebar', async () => { + createComponent(); + findParticipants().vm.$emit('toggleSidebar'); + + await nextTick(); + expect(wrapper.emitted('toggleSidebar')).toEqual([[]]); + }); + describe('when participants are loaded', () => { beforeEach(() => { createComponent({ diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index b71677a5e8f..d96573e26af 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -292,10 +292,37 @@ RSpec.describe Projects::TransferService do end end - context 'target namespace allows developers to create projects' do + context 'target namespace matches current namespace' do + let(:group) { user.namespace } + + it 'does not allow project transfer' do + transfer_result = execute_transfer + + expect(transfer_result).to eq false + expect(project.namespace).to eq(user.namespace) + expect(project.errors[:new_namespace]).to include('Project is already in this namespace.') + end + end + + context 'when user does not own the project' do + let(:project) { create(:project, :repository, :legacy_storage) } + + before do + project.add_developer(user) + end + + it 'does not allow project transfer to the target namespace' do + transfer_result = execute_transfer + + expect(transfer_result).to eq false + expect(project.errors[:new_namespace]).to include("You don't have permission to transfer this project.") + end + end + + context 'when user can create projects in the target namespace' do let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } - context 'the user is a member of the target namespace with developer permissions' do + context 'but has only developer permissions in the target namespace' do before do group.add_developer(user) end @@ -305,7 +332,7 @@ RSpec.describe Projects::TransferService do expect(transfer_result).to eq false expect(project.namespace).to eq(user.namespace) - expect(project.errors[:new_namespace]).to include('Transfer failed, please contact an admin.') + expect(project.errors[:new_namespace]).to include("You don't have permission to transfer projects into that namespace.") end end end