Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-04 15:15:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-04 15:15:02 +0300
commitf00510286b6ccda154c4926503397590a8851939 (patch)
treec4cd69ef2d0d6dd5abf30e3963ac00ead7421a19 /app
parent9e5c2e7342d1393f90e74a2ae4b3f27492c22e1f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci/pipeline_new/graphql/queries/ci_config_variables.graphql2
-rw-r--r--app/assets/javascripts/projects/default_project_templates.js4
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item.vue58
-rw-r--r--app/assets/javascripts/super_sidebar/components/pinned_section.vue97
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_menu.vue134
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue7
-rw-r--r--app/assets/javascripts/super_sidebar/constants.js2
-rw-r--r--app/assets/stylesheets/framework/super_sidebar.scss18
-rw-r--r--app/assets/stylesheets/page_bundles/escalation_policies.scss2
-rw-r--r--app/controllers/users/pins_controller.rb24
-rw-r--r--app/finders/ci/runners_finder.rb2
-rw-r--r--app/graphql/types/project_type.rb15
-rw-r--r--app/helpers/ci/catalog/resources_helper.rb4
-rw-r--r--app/helpers/sidebars_helper.rb7
-rw-r--r--app/models/bulk_imports/entity.rb9
-rw-r--r--app/models/ci/catalog/listing.rb2
-rw-r--r--app/models/pages/lookup_path.rb7
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/user_preference.rb2
-rw-r--r--app/policies/project_policy.rb20
-rw-r--r--app/services/bulk_imports/create_service.rb16
-rw-r--r--app/services/ci/catalog/add_resource_service.rb41
-rw-r--r--app/services/projects/update_pages_service.rb3
-rw-r--r--app/validators/json_schemas/pinned_nav_items.json22
-rw-r--r--app/views/layouts/_page.html.haml2
26 files changed, 432 insertions, 71 deletions
diff --git a/app/assets/javascripts/ci/pipeline_new/graphql/queries/ci_config_variables.graphql b/app/assets/javascripts/ci/pipeline_new/graphql/queries/ci_config_variables.graphql
index 648cd8b66b5..f93f5ad4f11 100644
--- a/app/assets/javascripts/ci/pipeline_new/graphql/queries/ci_config_variables.graphql
+++ b/app/assets/javascripts/ci/pipeline_new/graphql/queries/ci_config_variables.graphql
@@ -1,7 +1,7 @@
query ciConfigVariables($fullPath: ID!, $ref: String!) {
project(fullPath: $fullPath) {
id
- ciConfigVariables(sha: $ref) {
+ ciConfigVariables(ref: $ref) {
description
key
value
diff --git a/app/assets/javascripts/projects/default_project_templates.js b/app/assets/javascripts/projects/default_project_templates.js
index a44855c14d5..fb201576e85 100644
--- a/app/assets/javascripts/projects/default_project_templates.js
+++ b/app/assets/javascripts/projects/default_project_templates.js
@@ -121,4 +121,8 @@ export default {
text: s__('ProjectTemplates|TYPO3 Distribution'),
icon: '.template-option .icon-typo3',
},
+ laravel: {
+ text: s__('ProjectTemplates|Laravel Framework'),
+ icon: '.template-option .icon-laravel',
+ },
};
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue
index c3bd3b9b5d1..8248e11da33 100644
--- a/app/assets/javascripts/super_sidebar/components/nav_item.vue
+++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue
@@ -1,16 +1,36 @@
<script>
import { kebabCase } from 'lodash';
-import { GlCollapse, GlIcon, GlBadge } from '@gitlab/ui';
+import { GlButton, GlCollapse, GlIcon, GlBadge } from '@gitlab/ui';
+import { s__ } from '~/locale';
import { CLICK_MENU_ITEM_ACTION, TRACKING_UNKNOWN_ID } from '~/super_sidebar/constants';
export default {
+ i18n: {
+ pinItem: s__('Navigation|Pin item'),
+ unpinItem: s__('Navigation|Unpin item'),
+ },
name: 'NavItem',
components: {
+ GlButton,
GlCollapse,
GlIcon,
GlBadge,
},
+ inject: {
+ pinnedItemIds: { default: { ids: [] } },
+ panelSupportsPins: { default: false },
+ },
props: {
+ draggable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isStatic: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
item: {
type: Object,
required: true,
@@ -54,6 +74,12 @@ export default {
}
return this.item.is_active;
},
+ isPinnable() {
+ return this.panelSupportsPins && !this.isSection && !this.isStatic;
+ },
+ isPinned() {
+ return this.pinnedItemIds.ids.includes(this.item.id);
+ },
trackingProps() {
if (!this.item.id) {
return {
@@ -87,7 +113,10 @@ export default {
// Reset user agent styles on <button>
'gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left': this.isSection,
'gl-w-full gl-focus--focus': this.isSection,
+ 'nav-item-link': !this.isSection,
'gl-bg-t-gray-a-08': this.isActive,
+ 'gl-py-2': this.isPinnable,
+ 'gl-py-3': !this.isPinnable,
...this.linkClasses,
};
},
@@ -108,7 +137,7 @@ export default {
<component
:is="elem"
v-bind="linkProps"
- class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-text-decoration-none!"
+ class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-text-decoration-none!"
:class="computedLinkClasses"
data-qa-selector="sidebar_menu_link"
data-testid="nav-item-link"
@@ -124,6 +153,11 @@ export default {
<div class="gl-flex-shrink-0 gl-w-6 gl-mx-3">
<slot name="icon">
<gl-icon v-if="item.icon" :name="item.icon" class="gl-ml-2" />
+ <gl-icon
+ v-else-if="draggable"
+ name="grip"
+ class="gl-text-gray-400 gl-ml-2 draggable-icon"
+ />
</slot>
</div>
<div class="gl-pr-3 gl-text-gray-900 gl-truncate-end">
@@ -133,11 +167,27 @@ export default {
</div>
</div>
<slot name="actions"></slot>
- <span v-if="isSection || hasPill" class="gl-flex-grow-1 gl-text-right gl-mr-3">
+ <span v-if="isSection || hasPill || isPinnable" class="gl-flex-grow-1 gl-text-right gl-mr-3">
<gl-badge v-if="hasPill" size="sm" variant="info">
{{ pillData }}
</gl-badge>
<gl-icon v-else-if="isSection" :name="collapseIcon" />
+ <gl-button
+ v-else-if="isPinnable && !isPinned"
+ size="small"
+ category="tertiary"
+ icon="thumbtack"
+ :aria-label="$options.i18n.pinItem"
+ @click.prevent="$emit('pin-add', item.id)"
+ />
+ <gl-button
+ v-else-if="isPinnable && isPinned"
+ size="small"
+ category="tertiary"
+ :aria-label="$options.i18n.unpinItem"
+ icon="thumbtack-solid"
+ @click.prevent="$emit('pin-remove', item.id)"
+ />
</span>
</component>
<gl-collapse
@@ -152,6 +202,8 @@ export default {
v-for="subItem of item.items"
:key="`${item.title}-${subItem.title}`"
:item="subItem"
+ @pin-add="(itemId) => $emit('pin-add', itemId)"
+ @pin-remove="(itemId) => $emit('pin-remove', itemId)"
/>
</gl-collapse>
</li>
diff --git a/app/assets/javascripts/super_sidebar/components/pinned_section.vue b/app/assets/javascripts/super_sidebar/components/pinned_section.vue
new file mode 100644
index 00000000000..34aeae26d1f
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/pinned_section.vue
@@ -0,0 +1,97 @@
+<script>
+import { GlCollapse, GlIcon } from '@gitlab/ui';
+import Draggable from 'vuedraggable';
+import { s__ } from '~/locale';
+import NavItem from './nav_item.vue';
+
+export default {
+ i18n: {
+ pinned: s__('Navigation|Pinned'),
+ emptyHint: s__('Navigation|Your pinned items appear here.'),
+ },
+ name: 'PinnedSection',
+ components: {
+ Draggable,
+ GlCollapse,
+ GlIcon,
+ NavItem,
+ },
+ props: {
+ items: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+ data() {
+ return {
+ expanded: true,
+ draggableItems: this.items,
+ };
+ },
+ computed: {
+ collapseIcon() {
+ return this.expanded ? 'chevron-up' : 'chevron-down';
+ },
+ itemIds() {
+ return this.draggableItems.map((item) => item.id);
+ },
+ },
+ watch: {
+ items(newItems) {
+ this.draggableItems = newItems;
+ },
+ },
+ methods: {
+ handleDrag(event) {
+ if (event.oldIndex === event.newIndex) return;
+ this.$emit(
+ 'pin-reorder',
+ this.items[event.oldIndex].id,
+ this.items[event.newIndex].id,
+ event.oldIndex < event.newIndex,
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <section class="gl-mx-2">
+ <a
+ href="#"
+ class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-text-decoration-none!"
+ @click="expanded = !expanded"
+ >
+ <div class="gl-flex-shrink-0 gl-w-6 gl-mx-3">
+ <gl-icon name="thumbtack" class="gl-ml-2" />
+ </div>
+
+ <span class="gl-font-weight-bold gl-font-sm gl-flex-grow-1">{{ $options.i18n.pinned }}</span>
+ <gl-icon :name="collapseIcon" class="gl-mr-3" />
+ </a>
+ <gl-collapse v-model="expanded">
+ <draggable
+ v-if="items.length > 0"
+ v-model="draggableItems"
+ class="gl-p-0 gl-m-0"
+ data-testid="pinned-nav-items"
+ handle=".draggable-icon"
+ tag="ul"
+ @end="handleDrag"
+ >
+ <nav-item
+ v-for="item of draggableItems"
+ :key="item.id"
+ draggable
+ :item="item"
+ @pin-remove="(itemId) => $emit('pin-remove', itemId)"
+ />
+ </draggable>
+ <div v-else class="gl-text-secondary gl-font-sm gl-py-3" style="margin-left: 2.5rem">
+ {{ $options.i18n.emptyHint }}
+ </div>
+ </gl-collapse>
+ <hr class="gl-my-2" />
+ </section>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
index fc8968c50ea..843db691816 100644
--- a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
@@ -1,24 +1,156 @@
<script>
+import * as Sentry from '@sentry/browser';
+import axios from '~/lib/utils/axios_utils';
+import { PANELS_WITH_PINS } from '../constants';
import NavItem from './nav_item.vue';
+import PinnedSection from './pinned_section.vue';
export default {
name: 'SidebarMenu',
components: {
NavItem,
+ PinnedSection,
+ },
+
+ provide() {
+ return {
+ pinnedItemIds: this.changedPinnedItemIds,
+ panelSupportsPins: this.supportsPins,
+ };
},
props: {
items: {
type: Array,
required: true,
},
+ pinnedItemIds: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ panelType: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatePinsUrl: {
+ type: String,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ // This is used as a provide and injected into the nav items.
+ // Note: It has to be an object to be reactive.
+ changedPinnedItemIds: { ids: this.pinnedItemIds },
+ };
+ },
+
+ computed: {
+ // Returns the list of items that we want to have static at the top.
+ // Only sidebars that support pins also support a static section.
+ staticItems() {
+ if (!this.supportsPins) return [];
+ return this.items.filter((item) => !item.items || item.items.length === 0);
+ },
+
+ // Returns only the items that aren't static at the top and makes sure no
+ // section shows as active (and expanded) when one of its items is pinned.
+ nonStaticItems() {
+ if (!this.supportsPins) return this.items;
+
+ return this.items
+ .filter((item) => item.items && item.items.length > 0)
+ .map((item) => {
+ const hasActivePinnedChild = item.items.some((childItem) => {
+ return childItem.is_active && this.changedPinnedItemIds.ids.includes(childItem.id);
+ });
+ const showAsActive = item.is_active && !hasActivePinnedChild;
+
+ return { ...item, is_active: showAsActive };
+ });
+ },
+
+ // Returns a flat list of all items that are in sections, but not the sections.
+ // Only items from sections (item.items) can be pinned.
+ flatPinnableItems() {
+ return this.nonStaticItems.flatMap((item) => item.items).filter(Boolean);
+ },
+
+ pinnedItems() {
+ return this.changedPinnedItemIds.ids
+ .map((id) => this.flatPinnableItems.find((item) => item.id === id))
+ .filter(Boolean);
+ },
+ supportsPins() {
+ return PANELS_WITH_PINS.includes(this.panelType);
+ },
+ },
+ methods: {
+ createPin(itemId) {
+ this.changedPinnedItemIds.ids.push(itemId);
+ this.updatePins();
+ },
+ destroyPin(itemId) {
+ this.changedPinnedItemIds.ids = this.changedPinnedItemIds.ids.filter((id) => id !== itemId);
+ this.updatePins();
+ },
+ movePin(fromId, toId, isDownwards) {
+ const fromIndex = this.changedPinnedItemIds.ids.indexOf(fromId);
+ this.changedPinnedItemIds.ids.splice(fromIndex, 1);
+
+ let toIndex = this.changedPinnedItemIds.ids.indexOf(toId);
+
+ // If the item was moved downwards, we insert it *after* the item it was dragged on to.
+ // This matches how vuedraggable previews the change while still dragging.
+ if (isDownwards) toIndex += 1;
+
+ this.changedPinnedItemIds.ids.splice(toIndex, 0, fromId);
+
+ this.updatePins();
+ },
+ updatePins() {
+ axios
+ .put(this.updatePinsUrl, {
+ panel: this.panelType,
+ menu_item_ids: this.changedPinnedItemIds.ids,
+ })
+ .then((response) => {
+ this.changedPinnedItemIds.ids = response.data;
+ })
+ .catch((e) => {
+ Sentry.captureException(e);
+ });
+ },
},
};
</script>
<template>
<nav class="gl-py-2 gl-relative">
+ <section v-if="staticItems.length > 0" class="gl-mx-2">
+ <ul class="gl-p-0 gl-m-0">
+ <nav-item v-for="item in staticItems" :key="item.id" :item="item" is-static />
+ </ul>
+ <hr class="gl-my-2" />
+ </section>
+
+ <pinned-section
+ v-if="supportsPins"
+ :items="pinnedItems"
+ @pin-remove="destroyPin"
+ @pin-reorder="movePin"
+ />
+
<ul class="gl-px-2 gl-list-style-none">
- <nav-item v-for="item in items" :key="`menu-${item.title}`" :item="item" />
+ <nav-item
+ v-for="item in nonStaticItems"
+ :key="item.id"
+ :item="item"
+ @pin-add="createPin"
+ @pin-remove="destroyPin"
+ />
</ul>
</nav>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index b7a9583cae9..6807b4b2c7e 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -92,7 +92,12 @@ export default {
/>
</gl-collapse>
<gl-collapse :visible="!contextSwitcherOpen">
- <sidebar-menu :items="menuItems" />
+ <sidebar-menu
+ :items="menuItems"
+ :panel-type="sidebarData.panel_type"
+ :pinned-item-ids="sidebarData.pinned_items"
+ :update-pins-url="sidebarData.update_pins_url"
+ />
<sidebar-portal-target />
</gl-collapse>
</div>
diff --git a/app/assets/javascripts/super_sidebar/constants.js b/app/assets/javascripts/super_sidebar/constants.js
index ad9d4bc43f2..8290c4f533f 100644
--- a/app/assets/javascripts/super_sidebar/constants.js
+++ b/app/assets/javascripts/super_sidebar/constants.js
@@ -15,3 +15,5 @@ export const MAX_FREQUENT_GROUPS_COUNT = 3;
export const TRACKING_UNKNOWN_ID = 'item_without_id';
export const CLICK_MENU_ITEM_ACTION = 'click_menu_item';
+
+export const PANELS_WITH_PINS = ['group', 'project'];
diff --git a/app/assets/stylesheets/framework/super_sidebar.scss b/app/assets/stylesheets/framework/super_sidebar.scss
index 48c87682897..1431d8ed154 100644
--- a/app/assets/stylesheets/framework/super_sidebar.scss
+++ b/app/assets/stylesheets/framework/super_sidebar.scss
@@ -144,6 +144,24 @@
@include active-toggle;
}
}
+
+ .nav-item-link {
+ button,
+ .draggable-icon {
+ opacity: 0;
+ }
+
+ .draggable-icon {
+ cursor: grab;
+ }
+
+ &:hover {
+ button,
+ .draggable-icon {
+ opacity: 1;
+ }
+ }
+ }
}
.super-sidebar-skip-to {
diff --git a/app/assets/stylesheets/page_bundles/escalation_policies.scss b/app/assets/stylesheets/page_bundles/escalation_policies.scss
index 84c62ba93dd..5ca3dbcbcef 100644
--- a/app/assets/stylesheets/page_bundles/escalation_policies.scss
+++ b/app/assets/stylesheets/page_bundles/escalation_policies.scss
@@ -28,7 +28,7 @@ $stroke-size: 1px;
.escalation-rule-row {
@media (max-width: $breakpoint-lg) {
- @include gl-flex-wrap;
+ @include gl-flex-wrap-wrap;
}
}
diff --git a/app/controllers/users/pins_controller.rb b/app/controllers/users/pins_controller.rb
new file mode 100644
index 00000000000..81709dd4a2b
--- /dev/null
+++ b/app/controllers/users/pins_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Users
+ class PinsController < ApplicationController
+ feature_category :navigation
+ respond_to :json
+
+ def update
+ panel = pins_params[:panel]
+ pinned_nav_items = current_user.pinned_nav_items.merge({ panel => pins_params[:menu_item_ids] })
+ if current_user.update(pinned_nav_items: pinned_nav_items)
+ render json: current_user.pinned_nav_items[panel].to_json
+ else
+ head :bad_request
+ end
+ end
+
+ private
+
+ def pins_params
+ params.permit(:panel, menu_item_ids: [])
+ end
+ end
+end
diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index bc1dcb3ad5f..5f03ae77338 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -74,7 +74,7 @@ module Ci
end
def project_runners
- raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_project, @project)
+ raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :read_project_runners, @project)
@runners = ::Ci::Runner.owned_or_instance_wide(@project.id)
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index a67ead051c2..6530894562b 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -25,8 +25,13 @@ module Types
alpha: { milestone: '15.3' },
description: 'CI/CD config variable.' do
argument :sha, GraphQL::Types::String,
- required: true,
- description: 'Sha.'
+ required: false,
+ description: 'Sha.',
+ deprecated: { reason: 'Use `ref`', milestone: '15.11' }
+
+ argument :ref, GraphQL::Types::String,
+ required: false, # Make required when `sha` argument is removed
+ description: 'Ref.'
end
field :full_path, GraphQL::Types::ID,
@@ -645,8 +650,10 @@ module Types
# Even if the parameter name is `sha`, it is actually a ref name. We always send `ref` to the endpoint.
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/389065
- def ci_config_variables(sha:)
- result = ::Ci::ListConfigVariablesService.new(object, context[:current_user]).execute(sha)
+ # Remove `sha` argument and make `ref` required in a future release
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/404493
+ def ci_config_variables(ref: nil, sha: nil)
+ result = ::Ci::ListConfigVariablesService.new(object, context[:current_user]).execute(ref || sha)
return if result.nil?
diff --git a/app/helpers/ci/catalog/resources_helper.rb b/app/helpers/ci/catalog/resources_helper.rb
index c40c881aa20..9f70410f17f 100644
--- a/app/helpers/ci/catalog/resources_helper.rb
+++ b/app/helpers/ci/catalog/resources_helper.rb
@@ -3,7 +3,7 @@
module Ci
module Catalog
module ResourcesHelper
- def can_view_private_catalog?(_project)
+ def can_view_namespace_catalog?(_project)
false
end
@@ -13,3 +13,5 @@ module Ci
end
end
end
+
+Ci::Catalog::ResourcesHelper.prepend_mod_with('Ci::Catalog::ResourcesHelper')
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index a37c4e057b3..3769f03feb0 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -42,7 +42,7 @@ module SidebarsHelper
Sidebars::Context.new(**context_data, **args)
end
- def super_sidebar_context(user, group:, project:, panel:)
+ def super_sidebar_context(user, group:, project:, panel:, panel_type:) # rubocop:disable Metrics/AbcSize
{
current_menu_items: panel.super_sidebar_menu_items,
current_context_header: panel.super_sidebar_context_header,
@@ -84,7 +84,10 @@ module SidebarsHelper
canary_toggle_com_url: Gitlab::Saas.canary_toggle_com_url,
current_context: super_sidebar_current_context(project: project, group: group),
context_switcher_links: context_switcher_links,
- search: search_data
+ search: search_data,
+ pinned_items: user.pinned_nav_items[panel_type] || [],
+ panel_type: panel_type,
+ update_pins_url: pins_url
}
end
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index ae2d3758110..d5d1d38784e 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -44,23 +44,18 @@ class BulkImports::Entity < ApplicationRecord
validates :source_full_path,
presence: true,
format: { with: Gitlab::Regex.bulk_import_source_full_path_regex,
- message: Gitlab::Regex.bulk_import_destination_namespace_path_regex_message }
+ message: Gitlab::Regex.bulk_import_source_full_path_regex_message }
validates :destination_name,
presence: true,
- format: { with: Gitlab::Regex.group_path_regex,
- message: Gitlab::Regex.group_path_regex_message }
+ if: -> { group || project }
validates :destination_namespace,
exclusion: [nil],
- format: { with: Gitlab::Regex.bulk_import_destination_namespace_path_regex,
- message: Gitlab::Regex.bulk_import_destination_namespace_path_regex_message },
if: :group
validates :destination_namespace,
presence: true,
- format: { with: Gitlab::Regex.bulk_import_destination_namespace_path_regex,
- message: Gitlab::Regex.bulk_import_destination_namespace_path_regex_message },
if: :project
validate :validate_parent_is_a_group, if: :parent
diff --git a/app/models/ci/catalog/listing.rb b/app/models/ci/catalog/listing.rb
index 92464cb645f..b9e777f27a0 100644
--- a/app/models/ci/catalog/listing.rb
+++ b/app/models/ci/catalog/listing.rb
@@ -27,7 +27,7 @@ module Ci
def projects_in_namespace_visible_to_user
Project
.in_namespace(namespace.self_and_descendant_ids)
- .public_or_visible_to_user(current_user)
+ .public_or_visible_to_user(current_user, ::Gitlab::Access::DEVELOPER)
end
end
end
diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb
index ccf182e4b83..864ea04c019 100644
--- a/app/models/pages/lookup_path.rb
+++ b/app/models/pages/lookup_path.rb
@@ -61,6 +61,13 @@ module Pages
end
strong_memoize_attr :unique_host
+ def root_directory
+ return unless deployment
+
+ deployment.root_directory
+ end
+ strong_memoize_attr :root_directory
+
private
attr_reader :project, :trim_prefix, :domain
diff --git a/app/models/project.rb b/app/models/project.rb
index f49a1a65cba..9190e273151 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2409,6 +2409,8 @@ class Project < ApplicationRecord
.append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host)
.append(key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s)
.append(key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol)
+ .append(key: 'CI_SERVER_SHELL_SSH_HOST', value: Gitlab.config.gitlab_shell.ssh_host.to_s)
+ .append(key: 'CI_SERVER_SHELL_SSH_PORT', value: Gitlab.config.gitlab_shell.ssh_port.to_s)
.append(key: 'CI_SERVER_NAME', value: 'GitLab')
.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s)
diff --git a/app/models/user.rb b/app/models/user.rb
index ff131b9093b..c6afadb482f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -366,6 +366,7 @@ class User < ApplicationRecord
:diffs_addition_color, :diffs_addition_color=,
:use_legacy_web_ide, :use_legacy_web_ide=,
:use_new_navigation, :use_new_navigation=,
+ :pinned_nav_items, :pinned_nav_items=,
:achievements_enabled, :achievements_enabled=,
to: :user_preference
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index bc2c6b526b8..ecc64da2098 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -24,6 +24,8 @@ class UserPreference < ApplicationRecord
allow_blank: true
validates :use_legacy_web_ide, allow_nil: false, inclusion: { in: [true, false] }
+ validates :pinned_nav_items, json_schema: { filename: 'pinned_nav_items' }
+
ignore_columns :experience_level, remove_with: '14.10', remove_after: '2021-03-22'
attribute :tab_width, default: -> { Gitlab::TabWidth::DEFAULT }
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 8a0bbbba4d9..d6d0eefe3cd 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -242,6 +242,8 @@ class ProjectPolicy < BasePolicy
Feature.enabled?(:create_runner_workflow_for_namespace, project.namespace)
end
+ condition(:namespace_catalog_available) { namespace_catalog_available? }
+
# `:read_project` may be prevented in EE, but `:read_project_for_iids` should
# not.
rule { guest | admin }.enable :read_project_for_iids
@@ -261,7 +263,6 @@ class ProjectPolicy < BasePolicy
enable :reporter_access
enable :developer_access
enable :maintainer_access
- enable :add_catalog_resource
enable :change_namespace
enable :change_visibility_level
@@ -279,9 +280,6 @@ class ProjectPolicy < BasePolicy
enable :set_show_default_award_emojis
enable :set_show_diff_preview_in_email
enable :set_warn_about_potentially_unwanted_characters
-
- enable :register_project_runners
- enable :create_project_runners
enable :manage_owners
end
@@ -537,6 +535,8 @@ class ProjectPolicy < BasePolicy
enable :admin_feature_flags_client
enable :register_project_runners
enable :create_project_runners
+ enable :admin_project_runners
+ enable :read_project_runners
enable :update_runners_registration_token
enable :admin_project_google_cloud
enable :admin_project_aws
@@ -875,6 +875,14 @@ class ProjectPolicy < BasePolicy
# Should be matched with GroupPolicy#read_internal_note
rule { admin | can?(:reporter_access) }.enable :read_internal_note
+ rule { can?(:developer_access) & namespace_catalog_available }.policy do
+ enable :read_namespace_catalog
+ end
+
+ rule { can?(:owner_access) & namespace_catalog_available }.policy do
+ enable :add_catalog_resource
+ end
+
private
def user_is_user?
@@ -968,6 +976,10 @@ class ProjectPolicy < BasePolicy
def project
@subject
end
+
+ def namespace_catalog_available?
+ false
+ end
end
ProjectPolicy.prepend_mod_with('ProjectPolicy')
diff --git a/app/services/bulk_imports/create_service.rb b/app/services/bulk_imports/create_service.rb
index 348280030f4..aec32209b19 100644
--- a/app/services/bulk_imports/create_service.rb
+++ b/app/services/bulk_imports/create_service.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Entry point of the BulkImport feature.
+# Entry point of the BulkImport/Direct Transfer feature.
# This service receives a Gitlab Instance connection params
# and a list of groups to be imported.
#
@@ -84,6 +84,8 @@ module BulkImports
Array.wrap(params).each do |entity_params|
track_access_level(entity_params)
+ validate_destination_namespace(entity_params[:destination_namespace])
+ validate_destination_slug(entity_params[:destination_slug] || entity_params[:destination_name])
validate_destination_full_path(entity_params)
BulkImports::Entity.create!(
@@ -135,6 +137,18 @@ module BulkImports
credentials[:url].starts_with?(Settings.gitlab.base_url)
end
+ def validate_destination_namespace(destination_namespace)
+ return if destination_namespace =~ Gitlab::Regex.bulk_import_destination_namespace_path_regex
+
+ raise BulkImports::Error.destination_namespace_validation_failure
+ end
+
+ def validate_destination_slug(destination_slug)
+ return if destination_slug =~ Gitlab::Regex.oci_repository_path_regex
+
+ raise BulkImports::Error.destination_slug_validation_failure
+ end
+
def validate_destination_full_path(entity_params)
source_type = entity_params[:source_type]
diff --git a/app/services/ci/catalog/add_resource_service.rb b/app/services/ci/catalog/add_resource_service.rb
deleted file mode 100644
index 1f53513b7d1..00000000000
--- a/app/services/ci/catalog/add_resource_service.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-module Ci
- module Catalog
- class AddResourceService
- include Gitlab::Allowable
-
- attr_reader :project, :current_user
-
- def initialize(project, user)
- @current_user = user
- @project = project
- end
-
- def execute
- raise Gitlab::Access::AccessDeniedError unless can?(current_user, :add_catalog_resource, project)
-
- validation_response = Ci::Catalog::ValidateResourceService.new(project, project.default_branch).execute
-
- if validation_response.success?
- create_catalog_resource
- else
- ServiceResponse.error(message: validation_response.message)
- end
- end
-
- private
-
- def create_catalog_resource
- catalog_resource = Ci::Catalog::Resource.new(project: project)
-
- if catalog_resource.valid?
- catalog_resource.save!
- ServiceResponse.success(payload: catalog_resource)
- else
- ServiceResponse.error(message: catalog_resource.errors.full_messages.join(', '))
- end
- end
- end
- end
-end
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 0fadd75669e..403f645392c 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -90,7 +90,8 @@ module Projects
file: file,
file_count: deployment_update.entries_count,
file_sha256: sha256,
- ci_build_id: build.id
+ ci_build_id: build.id,
+ root_directory: build.options[:publish]
)
break if deployment.size != file.size || deployment.file.size != file.size
diff --git a/app/validators/json_schemas/pinned_nav_items.json b/app/validators/json_schemas/pinned_nav_items.json
new file mode 100644
index 00000000000..60dee5cc463
--- /dev/null
+++ b/app/validators/json_schemas/pinned_nav_items.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "description": "Pinned navigation items per panel",
+ "type": "object",
+ "properties": {
+ "group": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ },
+ "project": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 0b9072f6876..7e7f8a6a871 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -6,7 +6,7 @@
- group = @parent_group || @group
- sidebar_panel = super_sidebar_nav_panel(nav: nav, user: current_user, group: group, project: @project, current_ref: current_ref, ref_type: @ref_type, viewed_user: @user)
- - sidebar_data = super_sidebar_context(current_user, group: group, project: @project, panel: sidebar_panel).to_json
+ - sidebar_data = super_sidebar_context(current_user, group: group, project: @project, panel: sidebar_panel, panel_type: nav).to_json
%aside.js-super-sidebar.super-sidebar.super-sidebar-loading{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url } }
- if display_whats_new?