diff options
21 files changed, 526 insertions, 179 deletions
diff --git a/app/assets/javascripts/admin/users/components/users_table.vue b/app/assets/javascripts/admin/users/components/users_table.vue index c848908e024..ede5c26e487 100644 --- a/app/assets/javascripts/admin/users/components/users_table.vue +++ b/app/assets/javascripts/admin/users/components/users_table.vue @@ -107,7 +107,6 @@ export default { :items="users" :fields="$options.fields" :empty-text="s__('AdminUsers|No users found')" - data-qa-selector="user_row_content" show-empty stacked="md" :tbody-tr-attr="{ 'data-qa-selector': 'user_row_content' }" diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index 07ce2f3f93d..893b74a9895 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -1,7 +1,9 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { SIDEBAR_COLLAPSED_CLASS } from './contextual_sidebar'; +const isRefactoring = document.body.classList.contains('sidebar-refactoring'); const HIDE_INTERVAL_TIMEOUT = 300; +const COLLAPSED_PANEL_WIDTH = isRefactoring ? 48 : 50; const IS_OVER_CLASS = 'is-over'; const IS_ABOVE_CLASS = 'is-above'; const IS_SHOWING_FLY_OUT_CLASS = 'is-showing-fly-out'; @@ -22,12 +24,7 @@ export const setOpenMenu = (menu = null) => { export const slope = (a, b) => (b.y - a.y) / (b.x - a.x); -let headerHeight = 50; - -export const getHeaderHeight = () => headerHeight; -const setHeaderHeight = () => { - headerHeight = sidebar.offsetTop; -}; +export const getHeaderHeight = () => sidebar?.offsetTop || 0; export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains(SIDEBAR_COLLAPSED_CLASS); @@ -87,14 +84,20 @@ export const hideMenu = (el) => { }; export const moveSubItemsToPosition = (el, subItems) => { + const hasSubItems = subItems.parentNode.querySelector('.has-sub-items'); + const header = subItems.querySelector('.fly-out-top-item'); const boundingRect = el.getBoundingClientRect(); - const top = calculateTop(boundingRect, subItems.offsetHeight); - const left = sidebar ? sidebar.offsetWidth : 50; + const left = sidebar ? sidebar.offsetWidth : COLLAPSED_PANEL_WIDTH; + let top = calculateTop(boundingRect, subItems.offsetHeight); + if (isRefactoring && hasSubItems) { + top -= header.offsetHeight; + } else if (isRefactoring) { + top = boundingRect.top; + } const isAbove = top < boundingRect.top; subItems.classList.add('fly-out-list'); - subItems.style.transform = `translate3d(${left}px, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign - + subItems.style.transform = `translate3d(${left}px, ${Math.floor(top) - getHeaderHeight()}px, 0)`; // eslint-disable-line no-param-reassign const subItemsRect = subItems.getBoundingClientRect(); menuCornerLocs = [ @@ -188,8 +191,6 @@ export default () => { }); } - requestIdleCallback(setHeaderHeight); - items.forEach((el) => { const subItems = el.querySelector('.sidebar-sub-level-items'); diff --git a/app/assets/stylesheets/framework/contextual_sidebar_refactoring/contextual_sidebar_variant.scss b/app/assets/stylesheets/framework/contextual_sidebar_refactoring/contextual_sidebar_variant.scss index d0cddd5b565..154b8c31e8b 100644 --- a/app/assets/stylesheets/framework/contextual_sidebar_refactoring/contextual_sidebar_variant.scss +++ b/app/assets/stylesheets/framework/contextual_sidebar_refactoring/contextual_sidebar_variant.scss @@ -22,7 +22,7 @@ $top-level-item-color: $purple-900; color: $gray-darkest; } -&.ui-indigo .nav-sidebar li.active > a { +&.ui-indigo .nav-sidebar li.active:not(.fly-out-top-item) > a { color: $top-level-item-color; } @@ -99,15 +99,15 @@ $top-level-item-color: $purple-900; min-height: unset; } - .fly-out-top-item { - display: block; + .fly-out-top-item:not(.divider) { + display: block !important; } .avatar-container { margin: 0 auto; } - li.active > a { + li.active:not(.fly-out-top-item) > a { background-color: $indigo-900-alpha-008; } } @@ -115,38 +115,39 @@ $top-level-item-color: $purple-900; @mixin sub-level-items-flyout { .sidebar-sub-level-items { @include media-breakpoint-up(sm) { - position: fixed; - top: 0; - left: 0; + @include gl-fixed; + @include gl-top-0; + @include gl-left-0; + @include gl-ml-3; + @include gl-mt-0; + @include gl-px-0; + @include gl-pb-2; + @include gl-pt-0; min-width: 150px; - margin-top: -1px; - padding: 4px 1px; - background-color: $white; - box-shadow: 2px 1px 3px $dropdown-shadow-color; - border: 1px solid $gray-darker; - border-left: 0; - border-radius: 0 3px 3px 0; + background-color: $gray-10; + box-shadow: 0 $gl-spacing-scale-2 $gl-spacing-scale-5 $t-gray-a-24, 0 0 $gl-spacing-scale-1 $t-gray-a-24; + border-style: none; + border-radius: $border-radius-default; - &::before { - content: ''; - position: absolute; - top: -30px; - bottom: -30px; - left: -10px; - right: -30px; - z-index: -1; + .divider { + @include gl-display-none; } - &.is-above { - margin-top: 1px; + .divider + li > a { + @include gl-mt-2; } - .divider { - height: 1px; - margin: 4px -1px; - padding: 0; - background-color: $dropdown-divider-bg; + li:last-of-type a { + @include gl-mb-0; } + + &.is-above { + @include gl-mt-0; + } + } + + a { + @include gl-px-4; } .fly-out-top-item { @@ -200,6 +201,8 @@ $top-level-item-color: $purple-900; @include gl-align-items-center; @include gl-rounded-base; @include gl-w-auto; + @include gl-line-height-normal; + transition: none; margin: $sidebar-top-item-tb-margin $sidebar-top-item-lr-margin; &:hover { @@ -207,6 +210,52 @@ $top-level-item-color: $purple-900; } } +@mixin fly-out-top-item($has-sub-items: false) { + @include gl-display-none; + + a, + a:hover, + &.active a, + .fly-out-top-item-container { + @include gl-mx-0; + @include gl-px-5; + @include gl-cursor-default; + @include gl-pointer-events-none; + @include gl-font-sm; + background-color: $purple-900; + color: $white; + + @if $has-sub-items { + @include gl-mt-n2; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } @else { + @include gl-my-n2; + @include gl-mt-0; + @include gl-relative; + background-color: $black; + + strong { + @include gl-font-weight-normal; + } + + &::before { + @include gl-absolute; + content: ''; + display: block; + top: 50%; + left: $gl-spacing-scale-3/-2; + margin-top: -$gl-spacing-scale-3; + width: 0; + height: 0; + border-top: $gl-spacing-scale-3 solid transparent; + border-bottom: $gl-spacing-scale-3 solid transparent; + border-right: $gl-spacing-scale-3 solid $black; + } + } + } +} + // // PAGE-LAYOUT // @@ -258,7 +307,6 @@ $top-level-item-color: $purple-900; a { @include gl-text-decoration-none; - @include gl-line-height-normal; color: $top-level-item-color; } @@ -269,7 +317,8 @@ $top-level-item-color: $purple-900; flex: 1; } - > a { + > a, + > .fly-out-top-item-container { @include top-level-item; } @@ -278,8 +327,10 @@ $top-level-item-color: $purple-900; font-weight: $gl-font-weight-bold; } - > a:not(.has-sub-items) { - background-color: $indigo-900-alpha-008; + &:not(.fly-out-top-item) { + > a:not(.has-sub-items) { + background-color: $indigo-900-alpha-008; + } } } } @@ -298,8 +349,16 @@ $top-level-item-color: $purple-900; margin-right: 8px; } - .fly-out-top-item { - display: none; + a:not(.has-sub-items) + .sidebar-sub-level-items { + .fly-out-top-item { + @include fly-out-top-item($has-sub-items: false); + } + } + + a.has-sub-items + .sidebar-sub-level-items { + .fly-out-top-item { + @include fly-out-top-item($has-sub-items: true); + } } @media (min-width: map-get($grid-breakpoints, md)) and (max-width: map-get($grid-breakpoints, xl) - 1px) { @@ -356,8 +415,8 @@ $top-level-item-color: $purple-900; } .sidebar-sub-level-items { - @include gl-pb-0; - display: none; + @include gl-py-0; + @include gl-display-none; &:not(.fly-out-list) { li > a { diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index a16ae62b1c3..8622df34be6 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -1251,7 +1251,10 @@ body.sidebar-refactoring.gl-dark .sidebar-context-title { color: #c4c4c4; } -body.sidebar-refactoring.ui-indigo .nav-sidebar li.active > a { +body.sidebar-refactoring.ui-indigo + .nav-sidebar + li.active:not(.fly-out-top-item) + > a { color: #2f2a6b; } body.sidebar-refactoring.ui-indigo @@ -1325,20 +1328,22 @@ body.sidebar-refactoring } body.sidebar-refactoring .nav-sidebar.sidebar-collapsed-desktop - .fly-out-top-item { - display: block; + .fly-out-top-item:not(.divider) { + display: block !important; } body.sidebar-refactoring .nav-sidebar.sidebar-collapsed-desktop .avatar-container { margin: 0 auto; } -body.sidebar-refactoring .nav-sidebar.sidebar-collapsed-desktop li.active > a { +body.sidebar-refactoring + .nav-sidebar.sidebar-collapsed-desktop + li.active:not(.fly-out-top-item) + > a { background-color: rgba(41, 41, 97, 0.08); } body.sidebar-refactoring .nav-sidebar a { text-decoration: none; - line-height: 1rem; color: #2f2a6b; } body.sidebar-refactoring .nav-sidebar li { @@ -1347,7 +1352,8 @@ body.sidebar-refactoring .nav-sidebar li { body.sidebar-refactoring .nav-sidebar li .nav-item-name { flex: 1; } -body.sidebar-refactoring .nav-sidebar li > a { +body.sidebar-refactoring .nav-sidebar li > a, +body.sidebar-refactoring .nav-sidebar li > .fly-out-top-item-container { padding-left: 0.75rem; padding-right: 0.75rem; padding-top: 0.5rem; @@ -1356,12 +1362,16 @@ body.sidebar-refactoring .nav-sidebar li > a { align-items: center; border-radius: 0.25rem; width: auto; + line-height: 1rem; margin: 1px 4px; } body.sidebar-refactoring .nav-sidebar li.active > a { font-weight: 600; } -body.sidebar-refactoring .nav-sidebar li.active > a:not(.has-sub-items) { +body.sidebar-refactoring + .nav-sidebar + li.active:not(.fly-out-top-item) + > a:not(.has-sub-items) { background-color: rgba(41, 41, 97, 0.08); } body.sidebar-refactoring .nav-sidebar ul { @@ -1377,9 +1387,137 @@ body.sidebar-refactoring .nav-sidebar .nav-icon-container { display: flex; margin-right: 8px; } -body.sidebar-refactoring .nav-sidebar .fly-out-top-item { +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item { + display: none; +} +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + a, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item.active + a, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container { + margin-left: 0; + margin-right: 0; + padding-left: 1rem; + padding-right: 1rem; + cursor: default; + pointer-events: none; + font-size: 0.75rem; + background-color: #2f2a6b; + color: #333; + margin-top: -0.25rem; + margin-bottom: -0.25rem; + margin-top: 0; + position: relative; + background-color: #fff; +} +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + a + strong, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item.active + a + strong, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container + strong { + font-weight: 400; +} +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + a::before, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item.active + a::before, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container::before { + position: absolute; + content: ""; + display: block; + top: 50%; + left: -0.25rem; + margin-top: -0.5rem; + width: 0; + height: 0; + border-top: 0.5rem solid transparent; + border-bottom: 0.5rem solid transparent; + border-right: 0.5rem solid #fff; +} +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item { display: none; } +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item + a, +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item.active + a, +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container { + margin-left: 0; + margin-right: 0; + padding-left: 1rem; + padding-right: 1rem; + cursor: default; + pointer-events: none; + font-size: 0.75rem; + background-color: #2f2a6b; + color: #333; + margin-top: -0.25rem; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} @media (min-width: 768px) and (max-width: 1199px) { body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) { width: 48px; @@ -1417,8 +1555,8 @@ body.sidebar-refactoring .nav-sidebar .fly-out-top-item { } body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) - .fly-out-top-item { - display: block; + .fly-out-top-item:not(.divider) { + display: block !important; } body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) @@ -1427,7 +1565,7 @@ body.sidebar-refactoring .nav-sidebar .fly-out-top-item { } body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) - li.active + li.active:not(.fly-out-top-item) > a { background-color: rgba(41, 41, 97, 0.08); } @@ -1516,6 +1654,7 @@ body.sidebar-refactoring .nav-sidebar-inner-scroll > div.context-header a { align-items: center; border-radius: 0.25rem; width: auto; + line-height: 1rem; margin: 1px 4px; padding: 0.25rem; margin-bottom: 0.25rem; @@ -1617,6 +1756,7 @@ body.sidebar-refactoring color: #9dc7f1; } body.sidebar-refactoring .sidebar-sub-level-items { + padding-top: 0; padding-bottom: 0; display: none; } diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index 7ec99a6588f..a8d669a0c28 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -1213,7 +1213,10 @@ input { display: none; } } -body.sidebar-refactoring.ui-indigo .nav-sidebar li.active > a { +body.sidebar-refactoring.ui-indigo + .nav-sidebar + li.active:not(.fly-out-top-item) + > a { color: #2f2a6b; } body.sidebar-refactoring.ui-indigo @@ -1287,20 +1290,22 @@ body.sidebar-refactoring } body.sidebar-refactoring .nav-sidebar.sidebar-collapsed-desktop - .fly-out-top-item { - display: block; + .fly-out-top-item:not(.divider) { + display: block !important; } body.sidebar-refactoring .nav-sidebar.sidebar-collapsed-desktop .avatar-container { margin: 0 auto; } -body.sidebar-refactoring .nav-sidebar.sidebar-collapsed-desktop li.active > a { +body.sidebar-refactoring + .nav-sidebar.sidebar-collapsed-desktop + li.active:not(.fly-out-top-item) + > a { background-color: rgba(41, 41, 97, 0.08); } body.sidebar-refactoring .nav-sidebar a { text-decoration: none; - line-height: 1rem; color: #2f2a6b; } body.sidebar-refactoring .nav-sidebar li { @@ -1309,7 +1314,8 @@ body.sidebar-refactoring .nav-sidebar li { body.sidebar-refactoring .nav-sidebar li .nav-item-name { flex: 1; } -body.sidebar-refactoring .nav-sidebar li > a { +body.sidebar-refactoring .nav-sidebar li > a, +body.sidebar-refactoring .nav-sidebar li > .fly-out-top-item-container { padding-left: 0.75rem; padding-right: 0.75rem; padding-top: 0.5rem; @@ -1318,12 +1324,16 @@ body.sidebar-refactoring .nav-sidebar li > a { align-items: center; border-radius: 0.25rem; width: auto; + line-height: 1rem; margin: 1px 4px; } body.sidebar-refactoring .nav-sidebar li.active > a { font-weight: 600; } -body.sidebar-refactoring .nav-sidebar li.active > a:not(.has-sub-items) { +body.sidebar-refactoring + .nav-sidebar + li.active:not(.fly-out-top-item) + > a:not(.has-sub-items) { background-color: rgba(41, 41, 97, 0.08); } body.sidebar-refactoring .nav-sidebar ul { @@ -1339,9 +1349,137 @@ body.sidebar-refactoring .nav-sidebar .nav-icon-container { display: flex; margin-right: 8px; } -body.sidebar-refactoring .nav-sidebar .fly-out-top-item { +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item { + display: none; +} +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + a, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item.active + a, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container { + margin-left: 0; + margin-right: 0; + padding-left: 1rem; + padding-right: 1rem; + cursor: default; + pointer-events: none; + font-size: 0.75rem; + background-color: #2f2a6b; + color: #fff; + margin-top: -0.25rem; + margin-bottom: -0.25rem; + margin-top: 0; + position: relative; + background-color: #000; +} +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + a + strong, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item.active + a + strong, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container + strong { + font-weight: 400; +} +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + a::before, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item.active + a::before, +body.sidebar-refactoring + .nav-sidebar + a:not(.has-sub-items) + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container::before { + position: absolute; + content: ""; + display: block; + top: 50%; + left: -0.25rem; + margin-top: -0.5rem; + width: 0; + height: 0; + border-top: 0.5rem solid transparent; + border-bottom: 0.5rem solid transparent; + border-right: 0.5rem solid #000; +} +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item { display: none; } +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item + a, +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item.active + a, +body.sidebar-refactoring + .nav-sidebar + a.has-sub-items + + .sidebar-sub-level-items + .fly-out-top-item + .fly-out-top-item-container { + margin-left: 0; + margin-right: 0; + padding-left: 1rem; + padding-right: 1rem; + cursor: default; + pointer-events: none; + font-size: 0.75rem; + background-color: #2f2a6b; + color: #fff; + margin-top: -0.25rem; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} @media (min-width: 768px) and (max-width: 1199px) { body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) { width: 48px; @@ -1379,8 +1517,8 @@ body.sidebar-refactoring .nav-sidebar .fly-out-top-item { } body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) - .fly-out-top-item { - display: block; + .fly-out-top-item:not(.divider) { + display: block !important; } body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) @@ -1389,7 +1527,7 @@ body.sidebar-refactoring .nav-sidebar .fly-out-top-item { } body.sidebar-refactoring .nav-sidebar:not(.sidebar-expanded-mobile) - li.active + li.active:not(.fly-out-top-item) > a { background-color: rgba(41, 41, 97, 0.08); } @@ -1478,6 +1616,7 @@ body.sidebar-refactoring .nav-sidebar-inner-scroll > div.context-header a { align-items: center; border-radius: 0.25rem; width: auto; + line-height: 1rem; margin: 1px 4px; padding: 0.25rem; margin-bottom: 0.25rem; @@ -1579,6 +1718,7 @@ body.sidebar-refactoring color: #0b5cad; } body.sidebar-refactoring .sidebar-sub-level-items { + padding-top: 0; padding-bottom: 0; display: none; } diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index 29d9b4f5699..aecd8518336 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -161,7 +161,7 @@ class Packages::Package < ApplicationRecord after_commit :update_composer_cache, on: :destroy, if: -> { composer? } def self.only_maven_packages_with_path(path, use_cte: false) - if use_cte && Feature.enabled?(:maven_metadata_by_path_with_optimization_fence, default_enabled: :yaml) + if use_cte # This is an optimization fence which assumes that looking up the Metadatum record by path (globally) # and then filter down the packages (by project or by group and subgroups) will be cheaper than # looking up all packages within a project or group and filter them by path. diff --git a/app/views/groups/sidebar/_packages.html.haml b/app/views/groups/sidebar/_packages.html.haml index 7e0ee032aeb..e0158e4bf22 100644 --- a/app/views/groups/sidebar/_packages.html.haml +++ b/app/views/groups/sidebar/_packages.html.haml @@ -2,7 +2,7 @@ - if group_packages_nav? = nav_link(controller: ['groups/packages', 'groups/registry/repositories', 'groups/dependency_proxies']) do - = link_to packages_link, title: _('Packages') do + = link_to packages_link, title: _('Packages'), class: 'has-sub-items' do .nav-icon-container = sprite_icon('package') %span.nav-item-name diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index 1e0362ce065..2022c321a98 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -11,7 +11,7 @@ = _('Admin Area') %ul.sidebar-top-level-items{ data: { qa_selector: 'admin_sidebar_overview_submenu_content' } } = nav_link(controller: %w(dashboard admin admin/projects users groups jobs runners gitaly_servers), html_options: {class: 'home'}) do - = link_to admin_root_path do + = link_to admin_root_path, class: 'has-sub-items' do .nav-icon-container = sprite_icon('overview') %span.nav-item-name @@ -52,7 +52,7 @@ = _('Gitaly Servers') = nav_link(controller: admin_analytics_nav_links) do - = link_to admin_dev_ops_report_path, data: { qa_selector: 'admin_analytics_link' } do + = link_to admin_dev_ops_report_path, data: { qa_selector: 'admin_analytics_link' }, class: 'has-sub-items' do .nav-icon-container = sprite_icon('chart') %span.nav-item-name @@ -74,7 +74,7 @@ = _('Usage Trends') = nav_link(controller: admin_monitoring_nav_links) do - = link_to admin_system_info_path, data: { qa_selector: 'admin_monitoring_link' } do + = link_to admin_system_info_path, data: { qa_selector: 'admin_monitoring_link' }, class: 'has-sub-items' do .nav-icon-container = sprite_icon('monitor') %span.nav-item-name @@ -247,7 +247,7 @@ = _('Appearance') = nav_link(controller: [:application_settings, :integrations]) do - = link_to general_admin_application_settings_path do + = link_to general_admin_application_settings_path, class: 'has-sub-items' do .nav-icon-container = sprite_icon('settings') %span.nav-item-name.qa-admin-settings-item diff --git a/app/views/layouts/nav/sidebar/_analytics_links.html.haml b/app/views/layouts/nav/sidebar/_analytics_links.html.haml index 970a1d5f2c7..58989d6afc4 100644 --- a/app/views/layouts/nav/sidebar/_analytics_links.html.haml +++ b/app/views/layouts/nav/sidebar/_analytics_links.html.haml @@ -4,7 +4,7 @@ - if navbar_links.any? = nav_link(path: all_paths) do - = link_to analytics_link.link, {class: 'shortcuts-analytics', data: { qa_selector: 'analytics_anchor' } } do + = link_to analytics_link.link, {class: 'shortcuts-analytics has-sub-items', data: { qa_selector: 'analytics_anchor' } } do .nav-icon-container = sprite_icon('chart') %span.nav-item-name{ data: { qa_selector: 'analytics_link' } } diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 85a47603b51..63797ba6bee 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -19,7 +19,7 @@ - paths = group_overview_nav_link_paths = nav_link(path: paths, unless: -> { current_path?('groups/contribution_analytics#show') }, html_options: { class: 'home' }) do - information_link = sidebar_refactor_enabled? ? activity_group_path(@group) : group_path(@group) - = link_to information_link do + = link_to information_link, class: 'has-sub-items' do .nav-icon-container - sprite = sidebar_refactor_enabled? ? 'group' : 'home' = sprite_icon(sprite) @@ -62,7 +62,7 @@ - if group_sidebar_link?(:issues) = nav_link(path: group_issues_sub_menu_items, unless: -> { current_path?('issues_analytics#show') }) do - = link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' } do + = link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' }, class: 'has-sub-items' do .nav-icon-container = sprite_icon('issues') %span.nav-item-name @@ -157,7 +157,7 @@ - if group_sidebar_link?(:settings) = nav_link(path: group_settings_nav_link_paths) do - = link_to edit_group_path(@group) do + = link_to edit_group_path(@group), class: 'has-sub-items' do .nav-icon-container = sprite_icon('settings') %span.nav-item-name{ data: { qa_selector: 'group_settings' } } diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index c4b5c23be13..ec5d594abda 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -18,7 +18,7 @@ = f.label :auto_cancel_pending_pipelines, class: 'form-check-label' do %strong= _("Auto-cancel redundant pipelines") .form-text.text-muted - = _("New pipelines cause older pending pipelines on the same branch to be cancelled.") + = _("New pipelines cause older pending or running pipelines on the same branch to be cancelled.") = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'auto-cancel-redundant-pipelines'), target: '_blank' .form-group diff --git a/app/views/shared/nav/_sidebar_menu.html.haml b/app/views/shared/nav/_sidebar_menu.html.haml index 2d1d84706fa..b80bd515a32 100644 --- a/app/views/shared/nav/_sidebar_menu.html.haml +++ b/app/views/shared/nav/_sidebar_menu.html.haml @@ -16,11 +16,12 @@ %ul.sidebar-sub-level-items{ class: ('is-fly-out-only' unless sidebar_menu.has_renderable_items?) } = nav_link(**sidebar_menu.all_active_routes, html_options: { class: 'fly-out-top-item' } ) do - if sidebar_refactor_disabled? - = link_to sidebar_menu.link, **sidebar_menu.collapsed_container_html_options do + = link_to sidebar_menu.link, class: "'has-sub-items' if sidebar_menu.has_renderable_items?", **sidebar_menu.collapsed_container_html_options do = render 'shared/nav/sidebar_menu_collapsed', sidebar_menu: sidebar_menu - else - = render 'shared/nav/sidebar_menu_collapsed', sidebar_menu: sidebar_menu + %span.fly-out-top-item-container + = render 'shared/nav/sidebar_menu_collapsed', sidebar_menu: sidebar_menu - - if sidebar_menu.has_items? + - if sidebar_menu.has_renderable_items? %li.divider.fly-out-top-item = render partial: 'shared/nav/sidebar_menu_item', collection: sidebar_menu.renderable_items diff --git a/config/feature_flags/development/maven_metadata_by_path_with_optimization_fence.yml b/config/feature_flags/development/maven_metadata_by_path_with_optimization_fence.yml deleted file mode 100644 index 7b55cde4998..00000000000 --- a/config/feature_flags/development/maven_metadata_by_path_with_optimization_fence.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: maven_metadata_by_path_with_optimization_fence -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57041 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/325460 -milestone: '13.11' -type: development -group: group::optimize -default_enabled: true diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index 52aa0f5ab4e..a1c86ee4096 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -49,8 +49,12 @@ When a user is being [impersonated](../user/admin_area/index.md#user-impersonati ### Group events **(PREMIUM)** -A user with a Owner role (or above) can retrieve group audit events of all users. -A user with a Developer or Maintainer role is limited to group audit events based on their individual actions. +A user with: + +- Owner role (or above) can retrieve group audit events of all users. +- Developer or Maintainer role is limited to group audit events based on their individual actions. + +Group events do not include project audit events. To view a group's audit events, navigate to **Group > Security & Compliance > Audit Events**. From there, you can see the following actions: @@ -120,10 +124,11 @@ Server-wide audit events introduce the ability to observe user actions across the entire instance of your GitLab server, making it easy to understand who changed what and when for audit purposes. +Instance events do not include group or project audit events. + To view the server-wide administrator log, visit **Admin Area > Monitoring > Audit Events**. -In addition to the group and project events, the following user actions are also -recorded: +The following user actions are recorded: - Sign-in events and the authentication type (such as standard, LDAP, or OmniAuth) - Failed sign-ins diff --git a/doc/api/audit_events.md b/doc/api/audit_events.md index 93fefe50d9e..dec2b85c61d 100644 --- a/doc/api/audit_events.md +++ b/doc/api/audit_events.md @@ -11,6 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ## Instance Audit Events **(PREMIUM SELF)** The Audit Events API allows you to retrieve [instance audit events](../administration/audit_events.md#instance-events). +This API cannot retrieve group or project audit events. To retrieve audit events using the API, you must [authenticate yourself](README.md#authentication) as an Administrator. @@ -133,6 +134,7 @@ Example response: > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34078) in GitLab 12.5. The Group Audit Events API allows you to retrieve [group audit events](../administration/audit_events.md#group-events). +This API cannot retrieve project audit events. A user with a Owner role (or above) can retrieve group audit events of all users. A user with a Developer or Maintainer role is limited to group audit events based on their individual actions. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index ec5976c667c..4bf40e1c69a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -7229,6 +7229,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="boardepicchildreniid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". | | <a id="boardepicchildreniidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. | | <a id="boardepicchildreniids"></a>`iids` | [`[ID!]`](#id) | List of IIDs of epics, e.g., [1, 2]. | +| <a id="boardepicchildrenincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include epics from ancestor groups. | | <a id="boardepicchildrenincludedescendantgroups"></a>`includeDescendantGroups` | [`Boolean`](#boolean) | Include epics from descendant groups. | | <a id="boardepicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | @@ -8418,6 +8419,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="epicchildreniid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". | | <a id="epicchildreniidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. | | <a id="epicchildreniids"></a>`iids` | [`[ID!]`](#id) | List of IIDs of epics, e.g., [1, 2]. | +| <a id="epicchildrenincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include epics from ancestor groups. | | <a id="epicchildrenincludedescendantgroups"></a>`includeDescendantGroups` | [`Boolean`](#boolean) | Include epics from descendant groups. | | <a id="epicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | @@ -9020,6 +9022,7 @@ Returns [`Epic`](#epic). | <a id="groupepiciid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". | | <a id="groupepiciidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. | | <a id="groupepiciids"></a>`iids` | [`[ID!]`](#id) | List of IIDs of epics, e.g., [1, 2]. | +| <a id="groupepicincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include epics from ancestor groups. | | <a id="groupepicincludedescendantgroups"></a>`includeDescendantGroups` | [`Boolean`](#boolean) | Include epics from descendant groups. | | <a id="groupepiclabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="groupepicmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | @@ -9063,6 +9066,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="groupepicsiid"></a>`iid` | [`ID`](#id) | IID of the epic, e.g., "1". | | <a id="groupepicsiidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. | | <a id="groupepicsiids"></a>`iids` | [`[ID!]`](#id) | List of IIDs of epics, e.g., [1, 2]. | +| <a id="groupepicsincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include epics from ancestor groups. | | <a id="groupepicsincludedescendantgroups"></a>`includeDescendantGroups` | [`Boolean`](#boolean) | Include epics from descendant groups. | | <a id="groupepicslabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="groupepicsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | diff --git a/doc/api/groups.md b/doc/api/groups.md index c4dba255ba4..de2c6c95bcd 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -795,6 +795,10 @@ Parameters: | `shared_runners_minutes_limit` | integer | no | **(PREMIUM SELF)** Pipeline minutes quota for this group (included in plan). Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0` | | `extra_shared_runners_minutes_limit` | integer | no | **(PREMIUM SELF)** Extra pipeline minutes quota for this group (purchased in addition to the minutes included in the plan). | +NOTE: +On GitLab SaaS, you must use the GitLab UI to create groups without a parent group. You cannot +use the API to do this. + ### Options for `default_branch_protection` The `default_branch_protection` attribute determines whether developers and maintainers can push to the applicable [default branch](../user/project/repository/branches/default.md), as described in the following table: diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md index 67bdbffa2a1..871fb26ce08 100644 --- a/doc/development/documentation/structure.md +++ b/doc/development/documentation/structure.md @@ -109,7 +109,7 @@ Create an issue when you want to track bugs or future work. Prerequisites: -- A minimum of Contributor access to a project in GitLab. +- You must have at least the Developer role for a project. To create an issue: diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md index 1f2ef514536..2567cc3b828 100644 --- a/doc/user/packages/maven_repository/index.md +++ b/doc/user/packages/maven_repository/index.md @@ -611,8 +611,11 @@ Now navigate to your project's **Packages & Registries** page and view the publi ### Publishing a package with the same name or version -When you publish a package with the same name or version as an existing package, -the existing package is overwritten. +When you publish a package with the same name and version as an existing package, the new package +files are added to the existing package. You can still use the UI or API to access and view the +existing package's older files. + +To delete these older package versions, consider using the Packages API or the UI. #### Do not allow duplicate Maven packages diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2967ca71c83..540d738b2a8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22006,7 +22006,7 @@ msgstr "" msgid "New password" msgstr "" -msgid "New pipelines cause older pending pipelines on the same branch to be cancelled." +msgid "New pipelines cause older pending or running pipelines on the same branch to be cancelled." msgstr "" msgid "New project" @@ -28898,6 +28898,21 @@ msgstr "" msgid "SecurityOrchestration|Security policy project" msgstr "" +msgid "SecurityPolicies|Description" +msgstr "" + +msgid "SecurityPolicies|Enforcement status" +msgstr "" + +msgid "SecurityPolicies|Latest scan" +msgstr "" + +msgid "SecurityPolicies|Scan execution" +msgstr "" + +msgid "SecurityPolicies|view results" +msgstr "" + msgid "SecurityReports|%{firstProject} and %{secondProject}" msgstr "" diff --git a/spec/finders/packages/maven/package_finder_spec.rb b/spec/finders/packages/maven/package_finder_spec.rb index 13c603f1ec4..38fc3b7cce4 100644 --- a/spec/finders/packages/maven/package_finder_spec.rb +++ b/spec/finders/packages/maven/package_finder_spec.rb @@ -17,107 +17,89 @@ RSpec.describe ::Packages::Maven::PackageFinder do group.add_developer(user) end - shared_examples 'Packages::Maven::PackageFinder examples' do - describe '#execute!' do - subject { finder.execute! } + describe '#execute!' do + subject { finder.execute! } - shared_examples 'handling valid and invalid paths' do - context 'with a valid path' do - let(:param_path) { package.maven_metadatum.path } + shared_examples 'handling valid and invalid paths' do + context 'with a valid path' do + let(:param_path) { package.maven_metadatum.path } - it { is_expected.to eq(package) } - end - - context 'with an invalid path' do - let(:param_path) { 'com/example/my-app/1.0-SNAPSHOT' } - - it 'raises an error' do - expect { subject }.to raise_error(ActiveRecord::RecordNotFound) - end - end - - context 'with an uninstallable package' do - let(:param_path) { package.maven_metadatum.path } - - before do - package.update_column(:status, 1) - end - - it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) } - end + it { is_expected.to eq(package) } end - context 'within the project' do - let(:project_or_group) { project } + context 'with an invalid path' do + let(:param_path) { 'com/example/my-app/1.0-SNAPSHOT' } - it_behaves_like 'handling valid and invalid paths' - end - - context 'within a group' do - let(:project_or_group) { group } - - it_behaves_like 'handling valid and invalid paths' - end - - context 'across all projects' do it 'raises an error' do expect { subject }.to raise_error(ActiveRecord::RecordNotFound) end end - context 'versionless maven-metadata.xml package' do - let_it_be(:sub_group1) { create(:group, parent: group) } - let_it_be(:sub_group2) { create(:group, parent: group) } - let_it_be(:project1) { create(:project, group: sub_group1) } - let_it_be(:project2) { create(:project, group: sub_group2) } - let_it_be(:project3) { create(:project, group: sub_group1) } - let_it_be(:package_name) { 'foo' } - let_it_be(:package1) { create(:maven_package, project: project1, name: package_name, version: nil) } - let_it_be(:package2) { create(:maven_package, project: project2, name: package_name, version: nil) } - let_it_be(:package3) { create(:maven_package, project: project3, name: package_name, version: nil) } - - let(:project_or_group) { group } - let(:param_path) { package_name } + context 'with an uninstallable package' do + let(:param_path) { package.maven_metadatum.path } before do - sub_group1.add_developer(user) - sub_group2.add_developer(user) - # the package with the most recently published file should be returned - create(:package_file, :xml, package: package2) - end - - context 'without order by package file' do - it { is_expected.to eq(package3) } + package.update_column(:status, 1) end - context 'with order by package file' do - let(:param_order_by_package_file) { true } - - it { is_expected.to eq(package2) } - end + it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) } end end - end - context 'when the maven_metadata_by_path_with_optimization_fence feature flag is off' do - before do - stub_feature_flags(maven_metadata_by_path_with_optimization_fence: false) + context 'within the project' do + let(:project_or_group) { project } + + it_behaves_like 'handling valid and invalid paths' end - it_behaves_like 'Packages::Maven::PackageFinder examples' - end + context 'within a group' do + let(:project_or_group) { group } - context 'when the maven_metadata_by_path_with_optimization_fence feature flag is on' do - before do - stub_feature_flags(maven_metadata_by_path_with_optimization_fence: true) + it_behaves_like 'handling valid and invalid paths' end - it_behaves_like 'Packages::Maven::PackageFinder examples' + context 'across all projects' do + it 'raises an error' do + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end + end - it 'uses CTE in the query' do - sql = described_class.new(user, group, path: package.maven_metadatum.path).send(:packages).to_sql + context 'versionless maven-metadata.xml package' do + let_it_be(:sub_group1) { create(:group, parent: group) } + let_it_be(:sub_group2) { create(:group, parent: group) } + let_it_be(:project1) { create(:project, group: sub_group1) } + let_it_be(:project2) { create(:project, group: sub_group2) } + let_it_be(:project3) { create(:project, group: sub_group1) } + let_it_be(:package_name) { 'foo' } + let_it_be(:package1) { create(:maven_package, project: project1, name: package_name, version: nil) } + let_it_be(:package2) { create(:maven_package, project: project2, name: package_name, version: nil) } + let_it_be(:package3) { create(:maven_package, project: project3, name: package_name, version: nil) } + + let(:project_or_group) { group } + let(:param_path) { package_name } + + before do + sub_group1.add_developer(user) + sub_group2.add_developer(user) + # the package with the most recently published file should be returned + create(:package_file, :xml, package: package2) + end - expect(sql).to include('WITH "maven_metadata_by_path" AS') + context 'without order by package file' do + it { is_expected.to eq(package3) } + end + + context 'with order by package file' do + let(:param_order_by_package_file) { true } + + it { is_expected.to eq(package2) } + end end end + + it 'uses CTE in the query' do + sql = described_class.new(user, group, path: package.maven_metadatum.path).send(:packages).to_sql + + expect(sql).to include('WITH "maven_metadata_by_path" AS') + end end |