Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c70a70ea42
commit
bca3fb69e1
|
@ -0,0 +1,15 @@
|
|||
<!-- This template is used for proposing changes to the left sidebar contextual navigation. This could include additions, removals, or general changes to overall hierarchy.-->
|
||||
|
||||
### Proposal
|
||||
|
||||
<!-- Use this section to explain the proposed changes, including details around usage and business drivers. -->
|
||||
|
||||
### 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
|
|
@ -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].
|
||||
|
|
|
@ -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/).
|
||||
|
|
|
@ -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 <issue_id>`).**
|
||||
- [ ] 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
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
|
||||
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
|
||||
import { selectedRect as getSelectedRect } from 'prosemirror-tables';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'TableCellWrapper',
|
||||
components: {
|
||||
NodeViewWrapper,
|
||||
NodeViewContent,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownDivider,
|
||||
},
|
||||
props: {
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
getPos: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
displayActionsDropdown: false,
|
||||
preventHide: true,
|
||||
selectedRect: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
totalRows() {
|
||||
return this.selectedRect?.map.height;
|
||||
},
|
||||
totalCols() {
|
||||
return this.selectedRect?.map.width;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.editor.on('selectionUpdate', this.handleSelectionUpdate);
|
||||
this.handleSelectionUpdate();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.editor.off('selectionUpdate', this.handleSelectionUpdate);
|
||||
},
|
||||
methods: {
|
||||
handleSelectionUpdate() {
|
||||
const { state } = this.editor;
|
||||
const { $cursor } = state.selection;
|
||||
|
||||
this.displayActionsDropdown = $cursor?.pos - $cursor?.parentOffset - 1 === this.getPos();
|
||||
if (this.displayActionsDropdown) {
|
||||
this.selectedRect = getSelectedRect(state);
|
||||
}
|
||||
},
|
||||
runCommand(command) {
|
||||
this.editor.chain()[command]().run();
|
||||
this.hideDropdown();
|
||||
},
|
||||
handleHide($event) {
|
||||
if (this.preventHide) {
|
||||
$event.preventDefault();
|
||||
}
|
||||
this.preventHide = true;
|
||||
},
|
||||
hideDropdown() {
|
||||
this.preventHide = false;
|
||||
this.$refs.dropdown?.hide();
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
insertColumnBefore: __('Insert column before'),
|
||||
insertColumnAfter: __('Insert column after'),
|
||||
insertRowBefore: __('Insert row before'),
|
||||
insertRowAfter: __('Insert row after'),
|
||||
deleteRow: __('Delete row'),
|
||||
deleteColumn: __('Delete column'),
|
||||
deleteTable: __('Delete table'),
|
||||
editTableActions: __('Edit table'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<node-view-wrapper class="gl-relative gl-padding-5 gl-min-w-10" as="td" @click="hideDropdown">
|
||||
<span v-if="displayActionsDropdown" class="gl-absolute gl-right-0 gl-top-0">
|
||||
<gl-dropdown
|
||||
ref="dropdown"
|
||||
dropup
|
||||
icon="chevron-down"
|
||||
size="small"
|
||||
category="tertiary"
|
||||
boundary="viewport"
|
||||
no-caret
|
||||
text-sr-only
|
||||
:text="$options.i18n.editTableActions"
|
||||
:popper-opts="{ positionFixed: true }"
|
||||
@hide="handleHide($event)"
|
||||
>
|
||||
<gl-dropdown-item @click="runCommand('addColumnBefore')">
|
||||
{{ $options.i18n.insertColumnBefore }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item @click="runCommand('addColumnAfter')">
|
||||
{{ $options.i18n.insertColumnAfter }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item @click="runCommand('addRowBefore')">
|
||||
{{ $options.i18n.insertRowBefore }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item @click="runCommand('addRowAfter')">
|
||||
{{ $options.i18n.insertRowAfter }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider />
|
||||
<gl-dropdown-item v-if="totalRows > 2" @click="runCommand('deleteRow')">
|
||||
{{ $options.i18n.deleteRow }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="totalCols > 1" @click="runCommand('deleteColumn')">
|
||||
{{ $options.i18n.deleteColumn }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item @click="runCommand('deleteTable')">
|
||||
{{ $options.i18n.deleteTable }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</span>
|
||||
<node-view-content />
|
||||
</node-view-wrapper>
|
||||
</template>
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ Vue.use(Translate);
|
|||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
|
||||
});
|
||||
|
||||
export default () => {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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',
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -110,6 +110,7 @@ export default {
|
|||
:href="artifact.path"
|
||||
rel="nofollow"
|
||||
download
|
||||
class="gl-word-break-word"
|
||||
data-testid="artifact-item"
|
||||
>
|
||||
<gl-sprintf :message="$options.i18n.downloadArtifact">
|
||||
|
|
|
@ -56,6 +56,11 @@ export default {
|
|||
return this.$apollo.queries.participants.loading;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleSidebar() {
|
||||
this.$emit('toggleSidebar');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -66,5 +71,6 @@ export default {
|
|||
:number-of-less-participants="7"
|
||||
:lazy="false"
|
||||
class="block participants"
|
||||
@toggleSidebar="toggleSidebar"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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', () => {
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue