Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-13 12:15:20 +00:00
parent 62798ed33c
commit 944a3a7b7e
122 changed files with 955 additions and 891 deletions

View File

@ -11,7 +11,7 @@ include:
- local: .gitlab/ci/package-and-test/rules.gitlab-ci.yml
- local: .gitlab/ci/package-and-test/variables.gitlab-ci.yml
- project: gitlab-org/quality/pipeline-common
ref: 2.2.0
ref: 3.0.0
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml

View File

@ -1,6 +1,6 @@
include:
- project: gitlab-org/quality/pipeline-common
ref: 2.2.0
ref: 3.0.0
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml

View File

@ -180,7 +180,7 @@ Gitlab/StrongMemoizeAttr:
- 'app/services/ci/pipelines/hook_service.rb'
- 'app/services/ci/queue/build_queue_service.rb'
- 'app/services/ci/update_build_state_service.rb'
- 'app/services/clusters/agents/refresh_authorization_service.rb'
- 'app/services/clusters/agents/authorizations/ci_access/refresh_service.rb'
- 'app/services/clusters/integrations/prometheus_health_check_service.rb'
- 'app/services/concerns/alert_management/alert_processing.rb'
- 'app/services/concerns/incident_management/settings.rb'

View File

@ -105,7 +105,7 @@ Layout/LineLength:
- 'app/controllers/users_controller.rb'
- 'app/finders/analytics/cycle_analytics/stage_finder.rb'
- 'app/finders/ci/runners_finder.rb'
- 'app/finders/clusters/agent_authorizations_finder.rb'
- 'app/finders/clusters/agents/authorizations/ci_access/finder.rb'
- 'app/finders/group_descendants_finder.rb'
- 'app/finders/group_members_finder.rb'
- 'app/finders/group_projects_finder.rb'
@ -507,6 +507,7 @@ Layout/LineLength:
- 'app/services/ci/runners/register_runner_service.rb'
- 'app/services/ci/runners/unregister_runner_service.rb'
- 'app/services/clusters/agent_tokens/create_service.rb'
- 'app/services/clusters/agents/authorizations/ci_access/refresh_service.rb'
- 'app/services/clusters/agents/delete_service.rb'
- 'app/services/clusters/build_kubernetes_namespace_service.rb'
- 'app/services/clusters/integrations/create_service.rb'
@ -2038,7 +2039,7 @@ Layout/LineLength:
- 'ee/spec/models/ci/minutes/namespace_monthly_usage_spec.rb'
- 'ee/spec/models/ci/minutes/project_monthly_usage_spec.rb'
- 'ee/spec/models/ci/pipeline_spec.rb'
- 'ee/spec/models/concerns/ee/clusters/agents/authorization_config_scopes_spec.rb'
- 'ee/spec/models/concerns/ee/clusters/agents/authorizations/ci_access/config_scopes_spec.rb'
- 'ee/spec/models/concerns/ee/issuable_spec.rb'
- 'ee/spec/models/concerns/ee/noteable_spec.rb'
- 'ee/spec/models/concerns/ee/project_security_scanners_information_spec.rb'
@ -3516,6 +3517,8 @@ Layout/LineLength:
- 'spec/factories/ci/job_artifacts.rb'
- 'spec/factories/ci/pipelines.rb'
- 'spec/factories/ci/reports/codequality_degradations.rb'
- 'spec/factories/clusters/agents/authorizations/ci_access/group_authorizations.rb'
- 'spec/factories/clusters/agents/authorizations/ci_access/project_authorizations.rb'
- 'spec/factories/container_repositories.rb'
- 'spec/factories/dependency_proxy.rb'
- 'spec/factories/deployments.rb'
@ -3744,6 +3747,7 @@ Layout/LineLength:
- 'spec/finders/ci/pipelines_finder_spec.rb'
- 'spec/finders/ci/pipelines_for_merge_request_finder_spec.rb'
- 'spec/finders/ci/runners_finder_spec.rb'
- 'spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb'
- 'spec/finders/clusters/agent_authorizations_finder_spec.rb'
- 'spec/finders/clusters_finder_spec.rb'
- 'spec/finders/deploy_tokens/tokens_finder_spec.rb'
@ -4570,7 +4574,7 @@ Layout/LineLength:
- 'spec/models/concerns/cache_markdown_field_spec.rb'
- 'spec/models/concerns/cacheable_attributes_spec.rb'
- 'spec/models/concerns/ci/artifactable_spec.rb'
- 'spec/models/concerns/clusters/agents/authorization_config_scopes_spec.rb'
- 'spec/models/concerns/clusters/agents/authorizations/ci_access/config_scopes_spec.rb'
- 'spec/models/concerns/deployment_platform_spec.rb'
- 'spec/models/concerns/group_descendant_spec.rb'
- 'spec/models/concerns/id_in_ordered_spec.rb'
@ -5020,6 +5024,7 @@ Layout/LineLength:
- 'spec/services/ci/test_failure_history_service_spec.rb'
- 'spec/services/ci/unlock_artifacts_service_spec.rb'
- 'spec/services/ci/update_pending_build_service_spec.rb'
- 'spec/services/clusters/agents/authorizations/ci_access/filter_service_spec.rb'
- 'spec/services/clusters/create_service_spec.rb'
- 'spec/services/clusters/integrations/prometheus_health_check_service_spec.rb'
- 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb'

View File

@ -181,7 +181,7 @@ RSpec/ContextWording:
- 'ee/spec/finders/dast_site_profiles_finder_spec.rb'
- 'ee/spec/finders/dast_site_validations_finder_spec.rb'
- 'ee/spec/finders/ee/alert_management/http_integrations_finder_spec.rb'
- 'ee/spec/finders/ee/clusters/agent_authorizations_finder_spec.rb'
- 'ee/spec/finders/ee/clusters/agents/authorizations/ci_access/finder_spec.rb'
- 'ee/spec/finders/ee/clusters/agents_finder_spec.rb'
- 'ee/spec/finders/ee/group_members_finder_spec.rb'
- 'ee/spec/finders/ee/namespaces/projects_finder_spec.rb'
@ -1343,6 +1343,7 @@ RSpec/ContextWording:
- 'spec/finders/ci/pipelines_finder_spec.rb'
- 'spec/finders/ci/runners_finder_spec.rb'
- 'spec/finders/cluster_ancestors_finder_spec.rb'
- 'spec/finders/clusters/agents/authorizations/ci_access/finder_spec.rb'
- 'spec/finders/clusters/agent_authorizations_finder_spec.rb'
- 'spec/finders/clusters/agents_finder_spec.rb'
- 'spec/finders/clusters/kubernetes_namespace_finder_spec.rb'
@ -1528,7 +1529,7 @@ RSpec/ContextWording:
- 'spec/initializers/validate_database_config_spec.rb'
- 'spec/lib/api/entities/application_setting_spec.rb'
- 'spec/lib/api/entities/basic_project_details_spec.rb'
- 'spec/lib/api/entities/clusters/agent_authorization_spec.rb'
- 'spec/lib/api/entities/clusters/agents/authorizations/ci_access_spec.rb'
- 'spec/lib/api/entities/nuget/dependency_group_spec.rb'
- 'spec/lib/api/entities/user_spec.rb'
- 'spec/lib/api/every_api_endpoint_spec.rb'
@ -2685,7 +2686,7 @@ RSpec/ContextWording:
- 'spec/services/ci/update_pending_build_service_spec.rb'
- 'spec/services/clusters/agent_tokens/track_usage_service_spec.rb'
- 'spec/services/clusters/agents/delete_expired_events_service_spec.rb'
- 'spec/services/clusters/agents/refresh_authorization_service_spec.rb'
- 'spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb'
- 'spec/services/clusters/build_kubernetes_namespace_service_spec.rb'
- 'spec/services/clusters/create_service_spec.rb'
- 'spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb'

View File

@ -4544,7 +4544,6 @@ RSpec/MissingFeatureCategory:
- 'spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb'
- 'spec/lib/gitlab/slash_commands/presenters/run_spec.rb'
- 'spec/lib/gitlab/slash_commands/run_spec.rb'
- 'spec/lib/gitlab/slug/environment_spec.rb'
- 'spec/lib/gitlab/snippet_search_results_spec.rb'
- 'spec/lib/gitlab/sourcegraph_spec.rb'
- 'spec/lib/gitlab/spamcheck/client_spec.rb'
@ -5239,7 +5238,6 @@ RSpec/MissingFeatureCategory:
- 'spec/models/namespaces/user_namespace_spec.rb'
- 'spec/models/network/graph_spec.rb'
- 'spec/models/note_diff_file_spec.rb'
- 'spec/models/note_spec.rb'
- 'spec/models/notification_setting_spec.rb'
- 'spec/models/oauth_access_grant_spec.rb'
- 'spec/models/oauth_access_token_spec.rb'

View File

@ -54,7 +54,7 @@ Style/PercentLiteralDelimiters:
- 'app/models/ci/pipeline.rb'
- 'app/models/clusters/platforms/kubernetes.rb'
- 'app/models/commit.rb'
- 'app/models/concerns/clusters/agents/authorization_config_scopes.rb'
- 'app/models/concerns/clusters/agents/authorizations/ci_access/config_scopes.rb'
- 'app/models/concerns/diff_positionable_note.rb'
- 'app/models/concerns/enums/prometheus_metric.rb'
- 'app/models/concerns/issuable.rb'

View File

@ -121,7 +121,6 @@ Style/StringConcatenation:
- 'lib/gitlab/route_map.rb'
- 'lib/gitlab/sanitizers/exception_message.rb'
- 'lib/gitlab/sidekiq_logging/json_formatter.rb'
- 'lib/gitlab/slug/environment.rb'
- 'lib/gitlab/sql/set_operator.rb'
- 'lib/gitlab/ssh_public_key.rb'
- 'lib/gitlab/throttle.rb'

View File

@ -1 +1 @@
9255b4522513ee91d7fef535137d57deb498bbf8
28c0539251cf00a675b78e987ff30b7741425653

View File

@ -332,7 +332,7 @@ export default {
<template>
<div
class="design-detail js-design-detail fixed-top gl-w-full gl-bottom-0 gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row"
class="design-detail js-design-detail fixed-top gl-w-full gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row"
>
<div
class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative"

View File

@ -0,0 +1,47 @@
<script>
import { GlButton } from '@gitlab/ui';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import { __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
export default {
name: 'ExperimentHeader',
components: {
DeleteButton,
GlButton,
},
props: {
title: {
type: String,
required: true,
},
deleteInfo: {
type: Object,
required: true,
},
},
methods: {
downloadCsv() {
const currentPath = window.location.pathname;
const currentSearch = window.location.search;
visitUrl(`${currentPath}.csv${currentSearch}`);
},
},
i18n: {
downloadAsCsvLabel: __('Download as CSV'),
},
};
</script>
<template>
<div class="detail-page-header gl-flex-wrap-wrap">
<div class="detail-page-header-body">
<h1 class="page-title gl-font-size-h-display flex-fill">{{ title }}</h1>
<gl-button @click="downloadCsv">{{ $options.i18n.downloadAsCsvLabel }}</gl-button>
<delete-button v-bind="deleteInfo" />
</div>
</div>
</template>

View File

@ -12,7 +12,7 @@ import { queryToObject, setUrlParams, visitUrl } from '~/lib/utils/url_utility';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import KeysetPagination from '~/vue_shared/components/incubation/pagination.vue';
import IncubationAlert from '~/vue_shared/components/incubation/incubation_alert.vue';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import ExperimentHeader from './components/experiment_header.vue';
import {
LIST_KEY_CREATED_AT,
BASE_SORT_FIELDS,
@ -31,7 +31,7 @@ export default {
IncubationAlert,
RegistrySearch,
KeysetPagination,
DeleteButton,
ExperimentHeader,
},
props: {
experiment: {
@ -130,6 +130,14 @@ export default {
hasItems() {
return this.candidates.length > 0;
},
deleteButtonInfo() {
return {
deletePath: this.experiment.path,
deleteConfirmationText: translations.DELETE_EXPERIMENT_CONFIRMATION_MESSAGE,
actionPrimaryText: translations.DELETE_EXPERIMENT_PRIMARY_ACTION_LABEL,
modalTitle: translations.DELETE_EXPERIMENT_MODAL_TITLE,
};
},
},
methods: {
submitFilters() {
@ -163,20 +171,7 @@ export default {
:link-to-feedback-issue="$options.constants.FEATURE_FEEDBACK_ISSUE"
/>
<div class="detail-page-header gl-flex-wrap-wrap">
<div class="detail-page-header-body">
<h1 class="page-title gl-font-size-h-display flex-fill">
{{ experiment.name }}
</h1>
<delete-button
:delete-path="experiment.path"
:delete-confirmation-text="$options.i18n.DELETE_EXPERIMENT_CONFIRMATION_MESSAGE"
:action-primary-text="$options.i18n.DELETE_EXPERIMENT_PRIMARY_ACTION_LABEL"
:modal-title="$options.i18n.DELETE_EXPERIMENT_MODAL_TITLE"
/>
</div>
</div>
<experiment-header :title="experiment.name" :delete-info="deleteButtonInfo" />
<registry-search
:filters="filters"

View File

@ -22,7 +22,9 @@ export default {
...mapState(['navigation', 'urlQuery']),
},
created() {
this.fetchSidebarCount();
if (this.urlQuery?.search) {
this.fetchSidebarCount();
}
},
methods: {
...mapActions(['fetchSidebarCount']),

View File

@ -12,7 +12,7 @@ export const NAV_LINK_DEFAULT_CLASSES = [
'gl-justify-content-space-between',
];
export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal'];
export const HR_DEFAULT_CLASSES = ['gl-my-5', 'gl-mx-5', 'gl-border-gray-100'];
export const HR_DEFAULT_CLASSES = ['gl-m-5', 'gl-border-gray-100'];
export const ONLY_SHOW_MD = ['gl-display-none', 'gl-md-display-block'];
export const TRACKING_LABEL_CHECKBOX = 'Checkbox';

View File

@ -150,7 +150,12 @@ export default {
{{ $options.i18n.switchTo }}
</div>
<ul :aria-label="$options.i18n.switchTo" class="gl-p-0">
<nav-item v-for="item in persistentLinks" :key="item.link" :item="item" />
<nav-item
v-for="item in persistentLinks"
:key="item.link"
:item="item"
:link-classes="{ [item.link_classes]: item.link_classes }"
/>
</ul>
</li>
<projects-list

View File

@ -38,7 +38,7 @@ export default {
<button
v-collapse-toggle.context-switcher
type="button"
class="context-switcher-toggle gl-p-0 gl-bg-transparent gl-hover-bg-t-gray-a-08 gl-border-0 border-top border-bottom gl-border-gray-a-08 gl-box-shadow-none gl-display-flex gl-align-items-center gl-font-weight-bold gl-w-full gl-h-8"
class="context-switcher-toggle gl-p-0 gl-bg-transparent gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-border-0 border-top border-bottom gl-border-gray-a-08 gl-box-shadow-none gl-display-flex gl-align-items-center gl-font-weight-bold gl-w-full gl-h-8"
>
<span
v-if="context.icon"

View File

@ -36,11 +36,14 @@ export default {
storageKey() {
return `${this.username}/frequent-groups`;
},
viewAllItem() {
viewAllProps() {
return {
link: this.viewAllLink,
title: s__('Navigation|View all your groups'),
icon: 'group',
item: {
link: this.viewAllLink,
title: s__('Navigation|View all your groups'),
icon: 'group',
},
linkClasses: { 'dashboard-shortcuts-groups': true },
};
},
},
@ -61,7 +64,7 @@ export default {
:search-results="searchResults"
>
<template #view-all-items>
<nav-item :item="viewAllItem" />
<nav-item v-bind="viewAllProps" />
</template>
</search-results>
<frequent-items-list
@ -72,7 +75,7 @@ export default {
:pristine-text="$options.i18n.pristineText"
>
<template #view-all-items>
<nav-item :item="viewAllItem" />
<nav-item v-bind="viewAllProps" />
</template>
</frequent-items-list>
</template>

View File

@ -146,7 +146,7 @@ export default {
<component
:is="elem"
v-bind="linkProps"
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="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none!"
:class="computedLinkClasses"
data-qa-selector="nav_item_link"
data-testid="nav-item-link"

View File

@ -60,7 +60,7 @@ export default {
<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!"
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-focus-bg-t-gray-a-08 gl-text-decoration-none!"
@click.prevent="expanded = !expanded"
>
<div class="gl-flex-shrink-0 gl-w-6 gl-mx-3">

View File

@ -36,11 +36,14 @@ export default {
storageKey() {
return `${this.username}/frequent-projects`;
},
viewAllItem() {
viewAllProps() {
return {
link: this.viewAllLink,
title: s__('Navigation|View all your projects'),
icon: 'project',
item: {
link: this.viewAllLink,
title: s__('Navigation|View all your projects'),
icon: 'project',
},
linkClasses: { 'dashboard-shortcuts-projects': true },
};
},
},
@ -62,7 +65,7 @@ export default {
:search-results="searchResults"
>
<template #view-all-items>
<nav-item :item="viewAllItem" />
<nav-item v-bind="viewAllProps" />
</template>
</search-results>
<frequent-items-list
@ -73,7 +76,7 @@ export default {
:pristine-text="$options.i18n.pristineText"
>
<template #view-all-items>
<nav-item :item="viewAllItem" />
<nav-item v-bind="viewAllProps" />
</template>
</frequent-items-list>
</template>

View File

@ -108,5 +108,14 @@ export default {
</div>
</div>
</aside>
<a
v-for="shortcutLink in sidebarData.shortcut_links"
:key="shortcutLink.href"
:href="shortcutLink.href"
:class="shortcutLink.css_class"
class="gl-display-none"
>
{{ shortcutLink.title }}
</a>
</div>
</template>

View File

@ -137,7 +137,7 @@ export default {
<div class="gl-display-flex gl-justify-content-space-between gl-px-3 gl-py-2 gl-gap-2">
<counter
v-gl-tooltip:super-sidebar.hover.bottom="$options.i18n.issues"
class="gl-flex-basis-third"
class="gl-flex-basis-third dashboard-shortcuts-issues"
icon="issues"
:count="sidebarData.assigned_open_issues_count"
:href="sidebarData.issues_dashboard_path"
@ -165,7 +165,7 @@ export default {
</merge-request-menu>
<counter
v-gl-tooltip:super-sidebar.hover.bottom="$options.i18n.todoList"
class="gl-flex-basis-third"
class="gl-flex-basis-third shortcuts-todos"
icon="todo-done"
:count="sidebarData.todos_pending_count"
href="/dashboard/todos"

View File

@ -8,7 +8,7 @@ $search-input-field-x-min-width: 200px;
min-height: $header-height;
border: 0;
position: fixed;
top: 0;
top: $calc-application-bars-height;
left: 0;
right: 0;
border-radius: 0;
@ -479,11 +479,6 @@ $search-input-field-x-min-width: 200px;
visibility: visible;
}
.with-performance-bar .navbar-gitlab,
.with-performance-bar .fixed-top {
top: $performance-bar-height;
}
.navbar-empty {
justify-content: center;
height: $header-height;

View File

@ -36,27 +36,6 @@
}
}
// System Header
.with-system-header {
// main navigation
// login page
.navbar-gitlab,
.fixed-top {
top: $system-header-height;
}
// Performance Bar
// System Header
&.with-performance-bar {
// main navigation
header.navbar-gitlab,
.fixed-top {
top: $performance-bar-height + $system-header-height;
}
}
}
// System Footer
.with-system-footer {
// navless pages' footer eg: login page

View File

@ -20,10 +20,7 @@ $t-gray-a-16-design-pin: rgba($black, 0.16);
.design-detail {
background-color: rgba($modal-backdrop-bg, $modal-backdrop-opacity);
.with-performance-bar & {
top: 35px;
}
bottom: $calc-application-footer-height;
.comment-indicator {
border-radius: 50%;

View File

@ -21,18 +21,18 @@ $border-radius-medium: 3px;
}
}
.language-filter-checkbox {
.custom-control-label {
flex-grow: 1;
}
}
.search-sidebar {
@include media-breakpoint-up(md) {
min-width: $search-sidebar-min-width;
max-width: $search-sidebar-max-width;
}
.language-filter-checkbox {
.custom-control-label {
flex-grow: 1;
}
}
.language-filter-max-height {
max-height: $language-filter-max-height;
}

View File

@ -750,7 +750,7 @@ kbd {
min-height: var(--header-height, 48px);
border: 0;
position: fixed;
top: 0;
top: calc(var(--system-header-height) + var(--performance-bar-height));
left: 0;
right: 0;
border-radius: 0;

View File

@ -750,7 +750,7 @@ kbd {
min-height: var(--header-height, 48px);
border: 0;
position: fixed;
top: 0;
top: calc(var(--system-header-height) + var(--performance-bar-height));
left: 0;
right: 0;
border-radius: 0;

View File

@ -769,6 +769,9 @@ svg {
fill: currentColor;
}
.fixed-top {
top: calc(var(--system-header-height) + var(--performance-bar-height));
}
.gl-display-flex {
display: flex;
}

View File

@ -39,6 +39,12 @@
.border-radius-small { border-radius: $border-radius-small; }
.box-shadow-default { box-shadow: 0 2px 4px 0 $black-transparent; }
// Override Bootstrap class with offset for system-header and
// performance bar when present
.fixed-top {
top: $calc-application-bars-height;
}
.gl-children-ml-sm-3 > * {
@include media-breakpoint-up(sm) {
@include gl-ml-3;

View File

@ -1,69 +0,0 @@
# frozen_string_literal: true
module Clusters
class AgentAuthorizationsFinder
def initialize(project)
@project = project
end
def execute
# closest, most-specific authorization for a given agent wins
(project_authorizations + implicit_authorizations + group_authorizations)
.uniq(&:agent_id)
end
private
attr_reader :project
def implicit_authorizations
project.cluster_agents.map do |agent|
Clusters::Agents::ImplicitAuthorization.new(agent: agent)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def project_authorizations
namespace_ids = project.group ? all_namespace_ids : project.namespace_id
Clusters::Agents::ProjectAuthorization
.where(project_id: project.id)
.joins(agent: :project)
.preload(agent: :project)
.where(cluster_agents: { projects: { namespace_id: namespace_ids } })
.with_available_ci_access_fields(project)
.to_a
end
def group_authorizations
return [] unless project.group
authorizations = Clusters::Agents::GroupAuthorization.arel_table
ordered_ancestors_cte = Gitlab::SQL::CTE.new(
:ordered_ancestors,
project.group.self_and_ancestors(hierarchy_order: :asc).reselect(:id)
)
cte_join_sources = authorizations.join(ordered_ancestors_cte.table).on(
authorizations[:group_id].eq(ordered_ancestors_cte.table[:id])
).join_sources
Clusters::Agents::GroupAuthorization
.with(ordered_ancestors_cte.to_arel)
.joins(cte_join_sources)
.joins(agent: :project)
.with_available_ci_access_fields(project)
.where(projects: { namespace_id: all_namespace_ids })
.order(Arel.sql('agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], agent_group_authorizations.group_id)'))
.select('DISTINCT ON (agent_id) agent_group_authorizations.*')
.preload(agent: :project)
.to_a
end
# rubocop: enable CodeReuse/ActiveRecord
def all_namespace_ids
project.root_ancestor.self_and_descendants.select(:id)
end
end
end

View File

@ -0,0 +1,75 @@
# frozen_string_literal: true
module Clusters
module Agents
module Authorizations
module CiAccess
class Finder
def initialize(project)
@project = project
end
def execute
# closest, most-specific authorization for a given agent wins
(project_authorizations + implicit_authorizations + group_authorizations)
.uniq(&:agent_id)
end
private
attr_reader :project
def implicit_authorizations
project.cluster_agents.map do |agent|
Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization.new(agent: agent)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def project_authorizations
namespace_ids = project.group ? all_namespace_ids : project.namespace_id
Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization
.where(project_id: project.id)
.joins(agent: :project)
.preload(agent: :project)
.where(cluster_agents: { projects: { namespace_id: namespace_ids } })
.with_available_ci_access_fields(project)
.to_a
end
def group_authorizations
return [] unless project.group
authorizations = Clusters::Agents::Authorizations::CiAccess::GroupAuthorization.arel_table
ordered_ancestors_cte = Gitlab::SQL::CTE.new(
:ordered_ancestors,
project.group.self_and_ancestors(hierarchy_order: :asc).reselect(:id)
)
cte_join_sources = authorizations.join(ordered_ancestors_cte.table).on(
authorizations[:group_id].eq(ordered_ancestors_cte.table[:id])
).join_sources
Clusters::Agents::Authorizations::CiAccess::GroupAuthorization
.with(ordered_ancestors_cte.to_arel)
.joins(cte_join_sources)
.joins(agent: :project)
.with_available_ci_access_fields(project)
.where(projects: { namespace_id: all_namespace_ids })
.order(Arel.sql('agent_id, array_position(ARRAY(SELECT id FROM ordered_ancestors)::bigint[], agent_group_authorizations.group_id)'))
.select('DISTINCT ON (agent_id) agent_group_authorizations.*')
.preload(agent: :project)
.to_a
end
# rubocop: enable CodeReuse/ActiveRecord
def all_namespace_ids
project.root_ancestor.self_and_descendants.select(:id)
end
end
end
end
end
end

View File

@ -89,7 +89,8 @@ module SidebarsHelper
panel_type: panel_type,
update_pins_url: pins_url,
is_impersonating: impersonating?,
stop_impersonation_path: admin_impersonation_path
stop_impersonation_path: admin_impersonation_path,
shortcut_links: shortcut_links
}
end
@ -180,7 +181,8 @@ module SidebarsHelper
extraAttrs: {
'data-track-action': 'click_link',
'data-track-label': 'merge_requests_assigned',
'data-track-property': 'nav_core_menu'
'data-track-property': 'nav_core_menu',
class: 'dashboard-shortcuts-merge_requests'
}
},
{
@ -190,7 +192,8 @@ module SidebarsHelper
extraAttrs: {
'data-track-action': 'click_link',
'data-track-label': 'merge_requests_to_review',
'data-track-property': 'nav_core_menu'
'data-track-property': 'nav_core_menu',
class: 'dashboard-shortcuts-review_requests'
}
}
]
@ -334,6 +337,26 @@ module SidebarsHelper
def impersonating?
!!session[:impersonator_id]
end
def shortcut_links
[
{
title: _('Milestones'),
href: dashboard_milestones_path,
css_class: 'dashboard-shortcuts-milestones'
},
{
title: _('Snippets'),
href: dashboard_snippets_path,
css_class: 'dashboard-shortcuts-snippets'
},
{
title: _('Activity'),
href: activity_dashboard_path,
css_class: 'dashboard-shortcuts-activity'
}
]
end
end
SidebarsHelper.prepend_mod_with('SidebarsHelper')

View File

@ -11,9 +11,11 @@ module Ci
include ChronicDurationAttribute
include Gitlab::Utils::StrongMemoize
include IgnorableColumns
include SafelyChangeColumnDefault
self.table_name = 'p_ci_builds_metadata'
self.primary_key = 'id'
columns_changing_default :partition_id
partitionable scope: :build

View File

@ -1334,7 +1334,7 @@ module Ci
def cluster_agent_authorizations
strong_memoize(:cluster_agent_authorizations) do
::Clusters::AgentAuthorizationsFinder.new(project).execute
::Clusters::Agents::Authorizations::CiAccess::Finder.new(project).execute
end
end

View File

@ -12,11 +12,11 @@ module Clusters
has_many :agent_tokens, -> { order_last_used_at_desc }, class_name: 'Clusters::AgentToken', inverse_of: :agent
has_many :group_authorizations, class_name: 'Clusters::Agents::GroupAuthorization'
has_many :authorized_groups, class_name: '::Group', through: :group_authorizations, source: :group
has_many :ci_access_group_authorizations, class_name: 'Clusters::Agents::Authorizations::CiAccess::GroupAuthorization'
has_many :ci_access_authorized_groups, class_name: '::Group', through: :ci_access_group_authorizations, source: :group
has_many :project_authorizations, class_name: 'Clusters::Agents::ProjectAuthorization'
has_many :authorized_projects, class_name: '::Project', through: :project_authorizations, source: :project
has_many :ci_access_project_authorizations, class_name: 'Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization'
has_many :ci_access_authorized_projects, class_name: '::Project', through: :ci_access_project_authorizations, source: :project
has_many :activity_events, -> { in_timeline_order }, class_name: 'Clusters::Agents::ActivityEvent', inverse_of: :agent

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Clusters
module Agents
module Authorizations
module CiAccess
class GroupAuthorization < ApplicationRecord
include ConfigScopes
self.table_name = 'agent_group_authorizations'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :group, class_name: '::Group', optional: false
validates :config, json_schema: { filename: 'clusters_agents_authorizations_ci_access_config' }
def config_project
agent.project
end
end
end
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Clusters
module Agents
module Authorizations
module CiAccess
class ImplicitAuthorization
attr_reader :agent
delegate :id, to: :agent, prefix: true
def initialize(agent:)
@agent = agent
end
def config_project
agent.project
end
def config
{}
end
end
end
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Clusters
module Agents
module Authorizations
module CiAccess
class ProjectAuthorization < ApplicationRecord
include ConfigScopes
self.table_name = 'agent_project_authorizations'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :project, class_name: '::Project', optional: false
validates :config, json_schema: { filename: 'clusters_agents_authorizations_ci_access_config' }
def config_project
agent.project
end
end
end
end
end
end

View File

@ -1,20 +0,0 @@
# frozen_string_literal: true
module Clusters
module Agents
class GroupAuthorization < ApplicationRecord
include ::Clusters::Agents::AuthorizationConfigScopes
self.table_name = 'agent_group_authorizations'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :group, class_name: '::Group', optional: false
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
def config_project
agent.project
end
end
end
end

View File

@ -1,23 +0,0 @@
# frozen_string_literal: true
module Clusters
module Agents
class ImplicitAuthorization
attr_reader :agent
delegate :id, to: :agent, prefix: true
def initialize(agent:)
@agent = agent
end
def config_project
agent.project
end
def config
{}
end
end
end
end

View File

@ -1,20 +0,0 @@
# frozen_string_literal: true
module Clusters
module Agents
class ProjectAuthorization < ApplicationRecord
include ::Clusters::Agents::AuthorizationConfigScopes
self.table_name = 'agent_project_authorizations'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :project, class_name: '::Project', optional: false
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
def config_project
agent.project
end
end
end
end

View File

@ -1,25 +0,0 @@
# frozen_string_literal: true
module Clusters
module Agents
module AuthorizationConfigScopes
extend ActiveSupport::Concern
included do
scope :with_available_ci_access_fields, ->(project) {
where("config->'access_as' IS NULL")
.or(where("config->'access_as' = '{}'"))
.or(where("config->'access_as' ?| array[:fields]", fields: available_ci_access_fields(project)))
}
end
class_methods do
def available_ci_access_fields(_project)
%w(agent)
end
end
end
end
end
Clusters::Agents::AuthorizationConfigScopes.prepend_mod

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Clusters
module Agents
module Authorizations
module CiAccess
module ConfigScopes
extend ActiveSupport::Concern
included do
scope :with_available_ci_access_fields, ->(project) {
where("config->'access_as' IS NULL")
.or(where("config->'access_as' = '{}'"))
.or(where("config->'access_as' ?| array[:fields]", fields: available_ci_access_fields(project)))
}
end
class_methods do
def available_ci_access_fields(_project)
%w(agent)
end
end
end
end
end
end
end
Clusters::Agents::Authorizations::CiAccess::ConfigScopes.prepend_mod

View File

@ -41,7 +41,7 @@ module Ci
attr_reader :pipeline, :token, :environment, :template
def agent_authorizations
::Clusters::Agents::FilterAuthorizationsService.new(
::Clusters::Agents::Authorizations::CiAccess::FilterService.new(
pipeline.cluster_agent_authorizations,
environment: environment
).execute

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
module Clusters
module Agents
module Authorizations
module CiAccess
class FilterService
def initialize(authorizations, filter_params)
@authorizations = authorizations
@filter_params = filter_params
@environments_matcher = {}
end
def execute
filter_by_environment(authorizations)
end
private
attr_reader :authorizations, :filter_params
def filter_by_environment(auths)
return auths unless filter_by_environment?
auths.select do |auth|
next true if auth.config['environments'].blank?
auth.config['environments'].any? { |environment_pattern| matches_environment?(environment_pattern) }
end
end
def filter_by_environment?
filter_params.has_key?(:environment)
end
def environment_filter
@environment_filter ||= filter_params[:environment]
end
def matches_environment?(environment_pattern)
return false if environment_filter.nil?
environments_matcher(environment_pattern).match?(environment_filter)
end
def environments_matcher(environment_pattern)
@environments_matcher[environment_pattern] ||= ::Gitlab::Ci::EnvironmentMatcher.new(environment_pattern)
end
end
end
end
end
end

View File

@ -0,0 +1,106 @@
# frozen_string_literal: true
module Clusters
module Agents
module Authorizations
module CiAccess
class RefreshService
include Gitlab::Utils::StrongMemoize
AUTHORIZED_ENTITY_LIMIT = 100
delegate :project, to: :agent, private: true
delegate :root_ancestor, to: :project, private: true
def initialize(agent, config:)
@agent = agent
@config = config
end
def execute
refresh_projects!
refresh_groups!
true
end
private
attr_reader :agent, :config
def refresh_projects!
if allowed_project_configurations.present?
project_ids = allowed_project_configurations.map { |config| config.fetch(:project_id) }
agent.with_lock do
agent.ci_access_project_authorizations.upsert_all(allowed_project_configurations, unique_by: [:agent_id, :project_id])
agent.ci_access_project_authorizations.where.not(project_id: project_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord
end
else
agent.ci_access_project_authorizations.delete_all(:delete_all)
end
end
def refresh_groups!
if allowed_group_configurations.present?
group_ids = allowed_group_configurations.map { |config| config.fetch(:group_id) }
agent.with_lock do
agent.ci_access_group_authorizations.upsert_all(allowed_group_configurations, unique_by: [:agent_id, :group_id])
agent.ci_access_group_authorizations.where.not(group_id: group_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord
end
else
agent.ci_access_group_authorizations.delete_all(:delete_all)
end
end
def allowed_project_configurations
strong_memoize(:allowed_project_configurations) do
project_entries = extract_config_entries(entity: 'projects')
if project_entries
allowed_projects.where_full_path_in(project_entries.keys).map do |project|
{ project_id: project.id, config: project_entries[project.full_path.downcase] }
end
end
end
end
def allowed_group_configurations
strong_memoize(:allowed_group_configurations) do
group_entries = extract_config_entries(entity: 'groups')
if group_entries
allowed_groups.where_full_path_in(group_entries.keys).map do |group|
{ group_id: group.id, config: group_entries[group.full_path.downcase] }
end
end
end
end
def extract_config_entries(entity:)
config.dig('ci_access', entity)
&.first(AUTHORIZED_ENTITY_LIMIT)
&.index_by { |config| config.delete('id').downcase }
end
def allowed_projects
root_ancestor.all_projects
end
def allowed_groups
if group_root_ancestor?
root_ancestor.self_and_descendants
else
::Group.none
end
end
def group_root_ancestor?
root_ancestor.group_namespace?
end
end
end
end
end
end

View File

@ -57,7 +57,7 @@ module Clusters
def authorized_projects(user_access)
strong_memoize_with(:authorized_projects, user_access) do
user_access.fetch(:projects, [])
.first(::Clusters::Agents::RefreshAuthorizationService::AUTHORIZED_ENTITY_LIMIT)
.first(::Clusters::Agents::Authorizations::CiAccess::RefreshService::AUTHORIZED_ENTITY_LIMIT)
.map { |project| ::Project.find_by_full_path(project[:id]) }
.select { |project| current_user.can?(:use_k8s_proxies, project) }
end
@ -66,7 +66,7 @@ module Clusters
def authorized_groups(user_access)
strong_memoize_with(:authorized_groups, user_access) do
user_access.fetch(:groups, [])
.first(::Clusters::Agents::RefreshAuthorizationService::AUTHORIZED_ENTITY_LIMIT)
.first(::Clusters::Agents::Authorizations::CiAccess::RefreshService::AUTHORIZED_ENTITY_LIMIT)
.map { |group| ::Group.find_by_full_path(group[:id]) }
.select { |group| current_user.can?(:use_k8s_proxies, group) }
end

View File

@ -1,50 +0,0 @@
# frozen_string_literal: true
module Clusters
module Agents
class FilterAuthorizationsService
def initialize(authorizations, filter_params)
@authorizations = authorizations
@filter_params = filter_params
@environments_matcher = {}
end
def execute
filter_by_environment(authorizations)
end
private
attr_reader :authorizations, :filter_params
def filter_by_environment(auths)
return auths unless filter_by_environment?
auths.select do |auth|
next true if auth.config['environments'].blank?
auth.config['environments'].any? { |environment_pattern| matches_environment?(environment_pattern) }
end
end
def filter_by_environment?
filter_params.has_key?(:environment)
end
def environment_filter
@environment_filter ||= filter_params[:environment]
end
def matches_environment?(environment_pattern)
return false if environment_filter.nil?
environments_matcher(environment_pattern).match?(environment_filter)
end
def environments_matcher(environment_pattern)
@environments_matcher[environment_pattern] ||= ::Gitlab::Ci::EnvironmentMatcher.new(environment_pattern)
end
end
end
end

View File

@ -1,102 +0,0 @@
# frozen_string_literal: true
module Clusters
module Agents
class RefreshAuthorizationService
include Gitlab::Utils::StrongMemoize
AUTHORIZED_ENTITY_LIMIT = 100
delegate :project, to: :agent, private: true
delegate :root_ancestor, to: :project, private: true
def initialize(agent, config:)
@agent = agent
@config = config
end
def execute
refresh_projects!
refresh_groups!
true
end
private
attr_reader :agent, :config
def refresh_projects!
if allowed_project_configurations.present?
project_ids = allowed_project_configurations.map { |config| config.fetch(:project_id) }
agent.with_lock do
agent.project_authorizations.upsert_all(allowed_project_configurations, unique_by: [:agent_id, :project_id])
agent.project_authorizations.where.not(project_id: project_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord
end
else
agent.project_authorizations.delete_all(:delete_all)
end
end
def refresh_groups!
if allowed_group_configurations.present?
group_ids = allowed_group_configurations.map { |config| config.fetch(:group_id) }
agent.with_lock do
agent.group_authorizations.upsert_all(allowed_group_configurations, unique_by: [:agent_id, :group_id])
agent.group_authorizations.where.not(group_id: group_ids).delete_all # rubocop: disable CodeReuse/ActiveRecord
end
else
agent.group_authorizations.delete_all(:delete_all)
end
end
def allowed_project_configurations
strong_memoize(:allowed_project_configurations) do
project_entries = extract_config_entries(entity: 'projects')
if project_entries
allowed_projects.where_full_path_in(project_entries.keys).map do |project|
{ project_id: project.id, config: project_entries[project.full_path.downcase] }
end
end
end
end
def allowed_group_configurations
strong_memoize(:allowed_group_configurations) do
group_entries = extract_config_entries(entity: 'groups')
if group_entries
allowed_groups.where_full_path_in(group_entries.keys).map do |group|
{ group_id: group.id, config: group_entries[group.full_path.downcase] }
end
end
end
end
def extract_config_entries(entity:)
config.dig('ci_access', entity)
&.first(AUTHORIZED_ENTITY_LIMIT)
&.index_by { |config| config.delete('id').downcase }
end
def allowed_projects
root_ancestor.all_projects
end
def allowed_groups
if group_root_ancestor?
root_ancestor.self_and_descendants
else
::Group.none
end
end
def group_root_ancestor?
root_ancestor.group_namespace?
end
end
end
end

View File

@ -1,9 +1,5 @@
- search_bar_classes = 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4'
= render_if_exists 'shared/promotions/promote_advanced_search'
.results.gl-md-display-flex.gl-mt-0
#js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json } }
.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
= render partial: 'search/results_status' if @search_objects.present?
= render partial: 'search/results_list'
.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
= render partial: 'search/results_status' unless @search_objects.to_a.empty?
= render partial: 'search/results_list'

View File

@ -1,12 +1,14 @@
- @hide_top_links = true
- breadcrumb_title _('Search')
- page_title @search_term
- nav 'search'
- if params[:group_id].present?
= hidden_field_tag :group_id, params[:group_id]
- if params[:project_id].present?
= hidden_field_tag :project_id, params[:project_id]
- group_attributes = @group&.attributes&.slice('id', 'name')&.merge(full_name: @group&.full_name)
- project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace)
- search_bar_classes = 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4'
- if @search_results && !(@search_results.respond_to?(:failed?) && @search_results.failed?)
- if @search_service_presenter.without_count?
@ -20,5 +22,7 @@
= render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' }
#js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "elasticsearch-enabled": @search_service_presenter.advanced_search_enabled?.to_s, "default-branch-name": @project&.default_branch } }
- if @search_term
= render 'search/results'
.results.gl-md-display-flex.gl-mt-0
#js-search-sidebar{ class: search_bar_classes, data: { navigation_json: search_navigation_json } }
- if @search_term
= render 'search/results'

View File

@ -43,7 +43,6 @@
- dependency_firewall
- dependency_management
- dependency_proxy
- dependency_scanning
- deployment_management
- design_management
- design_system
@ -80,7 +79,6 @@
- interactive_application_security_testing
- internationalization
- kubernetes_management
- license_compliance
- logging
- merge_trains
- metrics
@ -123,6 +121,7 @@
- service_desk
- service_ping
- sm_provisioning
- software_composition_analysis
- source_code_management
- static_application_security_testing
- subgroups

View File

@ -1,7 +1,7 @@
---
table_name: agent_group_authorizations
classes:
- Clusters::Agents::GroupAuthorization
- Clusters::Agents::Authorizations::CiAccess::GroupAuthorization
feature_categories:
- kubernetes_management
description: Configuration for a group that is authorized to use a particular cluster agent

View File

@ -1,7 +1,7 @@
---
table_name: agent_project_authorizations
classes:
- Clusters::Agents::ProjectAuthorization
- Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization
feature_categories:
- kubernetes_management
description: Configuration for a project that is authorized to use a particular cluster agent

View File

@ -3,7 +3,7 @@ table_name: pm_checkpoints
classes:
- PackageMetadata::Checkpoint
feature_categories:
- license_compliance
- software_composition_analysis
description: Tracks position of last synced file.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109713
milestone: '15.9'

View File

@ -3,7 +3,7 @@ table_name: pm_licenses
classes:
- PackageMetadata::License
feature_categories:
- license_compliance
- software_composition_analysis
description: Tracks licenses referenced by public package registries.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794
milestone: '15.6'

View File

@ -3,7 +3,7 @@ table_name: pm_package_version_licenses
classes:
- PackageMetadata::PackageVersionLicense
feature_categories:
- license_compliance
- software_composition_analysis
description: Tracks licenses under which a given package version has been published.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794
milestone: '15.6'

View File

@ -3,7 +3,7 @@ table_name: pm_package_versions
classes:
- PackageMetadata::PackageVersion
feature_categories:
- license_compliance
- software_composition_analysis
description: Tracks package versions served by public package registries.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794
milestone: '15.6'

View File

@ -3,7 +3,7 @@ table_name: pm_packages
classes:
- PackageMetadata::Package
feature_categories:
- license_compliance
- software_composition_analysis
description: Tracks packages served by public package registries.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102794
milestone: '15.6'

View File

@ -3,7 +3,7 @@ table_name: project_security_settings
classes:
- ProjectSecuritySetting
feature_categories:
- dependency_scanning
- software_composition_analysis
- container_scanning
description: Project settings related to security features.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32577

View File

@ -4,8 +4,7 @@ classes:
- Vulnerabilities::Advisory
feature_categories:
- container_scanning
- dependency_scanning
- license_compliance
- software_composition_analysis
description: Stores vulnerability advisories
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95622
milestone: '15.4'

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class RemovePCiBuildsMetadataPartitionIdDefault < Gitlab::Database::Migration[2.1]
enable_lock_retries!
def up
change_column_default :p_ci_builds_metadata, :partition_id, from: 100, to: nil
end
def down
change_column_default :p_ci_builds_metadata, :partition_id, from: nil, to: 100
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class CleanupBigintConversionForSentNotifications < Gitlab::Database::Migration[2.1]
enable_lock_retries!
TABLE = :sent_notifications
COLUMNS = [:id]
def up
cleanup_conversion_of_integer_to_bigint(TABLE, COLUMNS)
end
def down
restore_conversion_of_integer_to_bigint(TABLE, COLUMNS)
end
end

View File

@ -0,0 +1 @@
d93a103c002a536d11f75256f20e2b8708ec760286f65d89ab5abe446fe629d4

View File

@ -0,0 +1 @@
f4472433ac5b74296409a04790d64ed56551358c98428ebb2e5f15d2f3e2db31

View File

@ -309,15 +309,6 @@ BEGIN
END;
$$;
CREATE FUNCTION trigger_7f4fcd5aa322() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW."id_convert_to_bigint" := NEW."id";
RETURN NEW;
END;
$$;
CREATE FUNCTION trigger_909cf0a06094() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -13054,7 +13045,7 @@ CREATE TABLE p_ci_builds_metadata (
id bigint NOT NULL,
runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL,
id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL,
partition_id bigint NOT NULL,
debug_trace_enabled boolean DEFAULT false NOT NULL
)
PARTITION BY LIST (partition_id);
@ -13083,7 +13074,7 @@ CREATE TABLE ci_builds_metadata (
id bigint DEFAULT nextval('ci_builds_metadata_id_seq'::regclass) NOT NULL,
runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL,
id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL,
partition_id bigint NOT NULL,
debug_trace_enabled boolean DEFAULT false NOT NULL
);
ALTER TABLE ONLY p_ci_builds_metadata ATTACH PARTITION ci_builds_metadata FOR VALUES IN ('100');
@ -22292,7 +22283,6 @@ CREATE SEQUENCE self_managed_prometheus_alert_events_id_seq
ALTER SEQUENCE self_managed_prometheus_alert_events_id_seq OWNED BY self_managed_prometheus_alert_events.id;
CREATE TABLE sent_notifications (
id_convert_to_bigint integer DEFAULT 0 NOT NULL,
project_id integer,
noteable_id integer,
noteable_type character varying,
@ -34227,8 +34217,6 @@ CREATE TRIGGER trigger_482bac5ec48a BEFORE INSERT OR UPDATE ON system_note_metad
CREATE TRIGGER trigger_775287b6d67a BEFORE INSERT OR UPDATE ON note_diff_files FOR EACH ROW EXECUTE FUNCTION trigger_775287b6d67a();
CREATE TRIGGER trigger_7f4fcd5aa322 BEFORE INSERT OR UPDATE ON sent_notifications FOR EACH ROW EXECUTE FUNCTION trigger_7f4fcd5aa322();
CREATE TRIGGER trigger_909cf0a06094 BEFORE INSERT OR UPDATE ON award_emoji FOR EACH ROW EXECUTE FUNCTION trigger_909cf0a06094();
CREATE TRIGGER trigger_bfc6e47be8cc BEFORE INSERT OR UPDATE ON snippet_user_mentions FOR EACH ROW EXECUTE FUNCTION trigger_bfc6e47be8cc();

View File

@ -324,30 +324,28 @@ GitLab generates audit events when a cluster agent token is created or revoked.
### Instance events **(PREMIUM SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16826) in GitLab 13.5, audit events for failed second-factor authentication attempt.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276250) in GitLab 13.6, audit events for when a user is approved using the Admin Area.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6, audit events for when a user's personal access token is successfully or unsuccessfully created or revoked.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298783) in GitLab 13.9, audit events for when a user requests access to an instance or is rejected using the Admin Area.
The following user actions on a GitLab instance generate instance audit events:
- Sign-in events and the authentication type (such as standard, LDAP, or OmniAuth)
- Failed sign-ins
- Added SSH key
- Added or removed email
- Changed password
- Ask for password reset
- Grant OAuth access
- Started or stopped user impersonation
- Changed username. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7797) in GitLab 12.8.
- User was deleted. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/251) in GitLab 12.8.
- User was added. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/251) in GitLab 12.8.
- User requests access to an instance. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298783) in GitLab 13.9.
- User was approved using the Admin Area. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276250) in GitLab 13.6.
- User was rejected using the Admin Area. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298783) in GitLab 13.9.
- User was blocked using the Admin Area. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/251) in GitLab 12.8.
- User was blocked using the API. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25872) in GitLab 12.9.
- Failed second-factor authentication attempt. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16826) in
GitLab 13.5.
- A user's personal access token was successfully created or revoked.
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6.
- A failed attempt to create or revoke a user's personal access token.
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6.
- Sign-in events and the authentication type such as standard, LDAP, or OmniAuth.
- Failed sign-ins.
- Added SSH key.
- Added or removed email.
- Changed password.
- Ask for password reset.
- Grant OAuth access.
- Started or stopped user impersonation.
- Changed username.
- User was added or deleted.
- User requests access to an instance.
- User was approved, rejected, or blocked using the Admin Area.
- User was blocked using the API.
- Failed second-factor authentication attempt.
- A user's personal access token was successfully or unsuccessfully created or revoked.
- Administrator added or removed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323905) in GitLab 14.1.
- Removed SSH key. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1.
- Added or removed GPG key. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1.

View File

@ -774,7 +774,7 @@ Example response:
}
```
## Reset instance's runner registration token
## Reset instance's runner registration token (deprecated)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3.
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104691) in GitLab 15.7.
@ -793,7 +793,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/runners/reset_registration_token"
```
## Reset project's runner registration token
## Reset project's runner registration token (deprecated)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3.
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104691) in GitLab 15.7.
@ -812,7 +812,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/projects/9/runners/reset_registration_token"
```
## Reset group's runner registration token
## Reset group's runner registration token (deprecated)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 14.3.
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104691) in GitLab 15.7.

View File

@ -149,8 +149,8 @@ For more information, see [Install the GitLab for Jira Cloud app](../connect-app
### Feature comparison of DVCS and GitLab for Jira Cloud app
| Feature | DVCS (Jira Cloud) | GitLab for Jira Cloud app |
|--------------------|--------------------|---------------------------------------|
| Feature | DVCS | GitLab for Jira Cloud app |
|--------------------|------------------------|---------------------------|
| Smart Commits | **{check-circle}** Yes | **{check-circle}** Yes |
| Sync MRs | **{check-circle}** Yes | **{check-circle}** Yes |
| Sync branches | **{check-circle}** Yes | **{check-circle}** Yes |

View File

@ -22,6 +22,9 @@ vulnerabilities. By including an extra Container Scanning job in your pipeline t
vulnerabilities and displays them in a merge request, you can use GitLab to audit your Docker-based
apps.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Container Scanning](https://www.youtube.com/watch?v=C0jn2eN5MAs).
Container Scanning is often considered part of Software Composition Analysis (SCA). SCA can contain
aspects of inspecting the items your code uses. These items typically include application and system
dependencies that are almost always imported from external sources, rather than sourced from items

View File

@ -35,6 +35,9 @@ vulnerability.
![Dependency scanning Widget](img/dependency_scanning_v13_2.png)
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Dependency Scanning](https://www.youtube.com/watch?v=TBnfbGk4c4o).
## Dependency Scanning compared to Container Scanning
GitLab offers both Dependency Scanning and Container Scanning

View File

@ -48,9 +48,9 @@ To open the Web IDE Beta from a merge request:
1. Go to your merge request.
1. In the upper-right corner, select **Code > Open in Web IDE**.
The Web IDE Beta opens modified and created files in separate tabs and displays changes side by side with the original source. To optimize loading time, only the top 10 files (by number of lines changed) are opened automatically.
The Web IDE Beta opens new and modified files in separate tabs and displays changes side by side with the original source. To optimize loading time, only the top 10 files (by number of lines changed) are opened automatically.
In the file tree, any modified or created file in the merge request is indicated by an icon next to the filename. To view changes to a file, right-click the filename and select **Compare with merge request base**.
In the file tree, any new or modified file in the merge request is indicated by an icon next to the filename. To view changes to a file, right-click the filename and select **Compare with merge request base**.
## Open a file in the Web IDE Beta

View File

@ -266,14 +266,14 @@ module API
persisted_environment = current_authenticated_job.actual_persisted_environment
environment = { tier: persisted_environment.tier, slug: persisted_environment.slug } if persisted_environment
agent_authorizations = ::Clusters::Agents::FilterAuthorizationsService.new(
::Clusters::AgentAuthorizationsFinder.new(project).execute,
agent_authorizations = ::Clusters::Agents::Authorizations::CiAccess::FilterService.new(
::Clusters::Agents::Authorizations::CiAccess::Finder.new(project).execute,
environment: persisted_environment&.name
).execute
# See https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/kubernetes_ci_access.md#apiv4joballowed_agents-api
{
allowed_agents: Entities::Clusters::AgentAuthorization.represent(agent_authorizations),
allowed_agents: Entities::Clusters::Agents::Authorizations::CiAccess.represent(agent_authorizations),
job: { id: current_authenticated_job.id },
pipeline: { id: pipeline.id },
project: { id: project.id, groups: project_groups },

View File

@ -1,13 +0,0 @@
# frozen_string_literal: true
module API
module Entities
module Clusters
class AgentAuthorization < Grape::Entity
expose :agent_id, as: :id
expose :config_project, with: Entities::ProjectIdentity
expose :config, as: :configuration
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module API
module Entities
module Clusters
module Agents
module Authorizations
class CiAccess < Grape::Entity
expose :agent_id, as: :id
expose :config_project, with: Entities::ProjectIdentity
expose :config, as: :configuration
end
end
end
end
end
end

View File

@ -129,7 +129,7 @@ module API
post '/', feature_category: :kubernetes_management, urgency: :low do
agent = ::Clusters::Agent.find(params[:agent_id])
::Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute
::Clusters::Agents::Authorizations::CiAccess::RefreshService.new(agent, config: params[:agent_config]).execute
no_content!
end

View File

@ -21,7 +21,7 @@ module Gitlab
slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
# Must start with a letter
slugified = 'env-' + slugified unless slugified.match?(/^[a-z]/)
slugified = "env-#{slugified}" unless slugified.match?(/^[a-z]/)
# Repeated dashes are invalid (OpenShift limitation)
slugified.squeeze!('-')

View File

@ -15493,6 +15493,9 @@ msgstr ""
msgid "Download artifacts"
msgstr ""
msgid "Download as CSV"
msgstr ""
msgid "Download codes"
msgstr ""
@ -39929,6 +39932,9 @@ msgstr ""
msgid "SecurityReports|Configure security testing"
msgstr ""
msgid "SecurityReports|Create Issue"
msgstr ""
msgid "SecurityReports|Create Jira issue"
msgstr ""
@ -39998,6 +40004,9 @@ msgstr ""
msgid "SecurityReports|Image"
msgstr ""
msgid "SecurityReports|Investigate this vulnerability by creating an issue"
msgstr ""
msgid "SecurityReports|Issue"
msgstr ""
@ -40151,6 +40160,9 @@ msgstr ""
msgid "SecurityReports|There was an error creating the issue."
msgstr ""
msgid "SecurityReports|There was an error creating the issue. Please try again."
msgstr ""
msgid "SecurityReports|There was an error creating the merge request."
msgstr ""

View File

@ -183,12 +183,6 @@ module ReviewApps
kubernetes.cleanup_namespaces_by_created_at(created_before: threshold_time(days: days)) unless dry_run
end
def perform_stale_pvc_cleanup!(days:)
puts "Dry-run mode." if dry_run
kubernetes.cleanup_pvcs_by_created_at(created_before: threshold_time(days: days)) unless dry_run
end
private
attr_reader :api_endpoint, :dry_run, :gitlab_token, :project_path
@ -322,10 +316,4 @@ if $PROGRAM_NAME == __FILE__
timed('Stale Namespace cleanup') do
automated_cleanup.perform_stale_namespace_cleanup!(days: 3)
end
puts
timed('Stale PVC cleanup') do
automated_cleanup.perform_stale_pvc_cleanup!(days: 30)
end
end

View File

@ -15,6 +15,9 @@ function k8s_resource_count() {
SERVICES_COUNT_THRESHOLD=3000
REVIEW_APPS_COUNT_THRESHOLD=200
# One review app currently deploys 4 PVCs
PVCS_COUNT_THRESHOLD=$((REVIEW_APPS_COUNT_THRESHOLD * 4))
exit_with_error=false
# In the current GKE cluster configuration, we should never go higher than 4096 services per cluster.
@ -36,6 +39,12 @@ if [ "$(echo $(($namespaces_count - $review_apps_count)) | sed 's/-//')" -gt 30
exit_with_error=true
fi
pvcs_count=$(kubectl get pvc -A | wc -l | xargs)
if [ "${pvcs_count}" -gt "${PVCS_COUNT_THRESHOLD}" ]; then
>&2 echo "❌ [ERROR] PVCs are above ${PVCS_COUNT_THRESHOLD} (currently at ${pvcs_count})"
exit_with_error=true
fi
if [ "${exit_with_error}" = true ] ; then
exit 1
fi

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
FactoryBot.define do
factory :agent_group_authorization, class: 'Clusters::Agents::GroupAuthorization' do
factory :agent_ci_access_group_authorization, class: 'Clusters::Agents::Authorizations::CiAccess::GroupAuthorization' do
association :agent, factory: :cluster_agent
group

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
FactoryBot.define do
factory :agent_project_authorization, class: 'Clusters::Agents::ProjectAuthorization' do
factory :agent_ci_access_project_authorization, class: 'Clusters::Agents::Authorizations::CiAccess::ProjectAuthorization' do
association :agent, factory: :cluster_agent
project

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Clusters::AgentAuthorizationsFinder do
RSpec.describe Clusters::Agents::Authorizations::CiAccess::Finder, feature_category: :kubernetes_management do
describe '#execute' do
let_it_be(:top_level_group) { create(:group) }
let_it_be(:subgroup1) { create(:group, parent: top_level_group) }
@ -54,34 +54,34 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
let(:unrelated_agent) { create(:cluster_agent) }
before do
create(:agent_project_authorization, agent: unrelated_agent, project: requesting_project)
create(:agent_ci_access_project_authorization, agent: unrelated_agent, project: requesting_project)
end
it { is_expected.to be_empty }
end
context 'agent configuration project shares a root namespace, but does not belong to an ancestor of the given project' do
let!(:project_authorization) { create(:agent_project_authorization, agent: non_ancestor_agent, project: requesting_project) }
let!(:project_authorization) { create(:agent_ci_access_project_authorization, agent: non_ancestor_agent, project: requesting_project) }
it { is_expected.to match_array([project_authorization]) }
end
context 'with project authorizations present' do
let!(:authorization) { create(:agent_project_authorization, agent: production_agent, project: requesting_project) }
let!(:authorization) { create(:agent_ci_access_project_authorization, agent: production_agent, project: requesting_project) }
it { is_expected.to match_array [authorization] }
end
context 'with overlapping authorizations' do
let!(:agent) { create(:cluster_agent, project: requesting_project) }
let!(:project_authorization) { create(:agent_project_authorization, agent: agent, project: requesting_project) }
let!(:group_authorization) { create(:agent_group_authorization, agent: agent, group: bottom_level_group) }
let!(:project_authorization) { create(:agent_ci_access_project_authorization, agent: agent, project: requesting_project) }
let!(:group_authorization) { create(:agent_ci_access_group_authorization, agent: agent, group: bottom_level_group) }
it { is_expected.to match_array [project_authorization] }
end
it_behaves_like 'access_as' do
let!(:authorization) { create(:agent_project_authorization, agent: production_agent, project: requesting_project, config: config) }
let!(:authorization) { create(:agent_ci_access_project_authorization, agent: production_agent, project: requesting_project, config: config) }
end
end
@ -92,7 +92,7 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
expect(subject.count).to eq(1)
authorization = subject.first
expect(authorization).to be_a(Clusters::Agents::ImplicitAuthorization)
expect(authorization).to be_a(Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization)
expect(authorization.agent).to eq(associated_agent)
end
end
@ -102,15 +102,15 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
let(:unrelated_agent) { create(:cluster_agent) }
before do
create(:agent_group_authorization, agent: unrelated_agent, group: top_level_group)
create(:agent_ci_access_group_authorization, agent: unrelated_agent, group: top_level_group)
end
it { is_expected.to be_empty }
end
context 'multiple agents are authorized for the same group' do
let!(:staging_auth) { create(:agent_group_authorization, agent: staging_agent, group: bottom_level_group) }
let!(:production_auth) { create(:agent_group_authorization, agent: production_agent, group: bottom_level_group) }
let!(:staging_auth) { create(:agent_ci_access_group_authorization, agent: staging_agent, group: bottom_level_group) }
let!(:production_auth) { create(:agent_ci_access_group_authorization, agent: production_agent, group: bottom_level_group) }
it 'returns authorizations for all agents' do
expect(subject).to contain_exactly(staging_auth, production_auth)
@ -118,8 +118,8 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
end
context 'a single agent is authorized to more than one matching group' do
let!(:bottom_level_auth) { create(:agent_group_authorization, agent: production_agent, group: bottom_level_group) }
let!(:top_level_auth) { create(:agent_group_authorization, agent: production_agent, group: top_level_group) }
let!(:bottom_level_auth) { create(:agent_ci_access_group_authorization, agent: production_agent, group: bottom_level_group) }
let!(:top_level_auth) { create(:agent_ci_access_group_authorization, agent: production_agent, group: top_level_group) }
it 'picks the authorization for the closest group to the requesting project' do
expect(subject).to contain_exactly(bottom_level_auth)
@ -127,13 +127,13 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
end
context 'agent configuration project does not belong to an ancestor of the authorized group' do
let!(:group_authorization) { create(:agent_group_authorization, agent: non_ancestor_agent, group: bottom_level_group) }
let!(:group_authorization) { create(:agent_ci_access_group_authorization, agent: non_ancestor_agent, group: bottom_level_group) }
it { is_expected.to match_array([group_authorization]) }
end
it_behaves_like 'access_as' do
let!(:authorization) { create(:agent_group_authorization, agent: production_agent, group: top_level_group, config: config) }
let!(:authorization) { create(:agent_ci_access_group_authorization, agent: production_agent, group: top_level_group, config: config) }
end
end
end

View File

@ -2,7 +2,7 @@
exports[`Design management design index page renders design index 1`] = `
<div
class="design-detail js-design-detail fixed-top gl-w-full gl-bottom-0 gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row"
class="design-detail js-design-detail fixed-top gl-w-full gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row"
>
<div
class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative"
@ -115,7 +115,7 @@ exports[`Design management design index page renders design index 1`] = `
exports[`Design management design index page with error GlAlert is rendered in correct position with correct content 1`] = `
<div
class="design-detail js-design-detail fixed-top gl-w-full gl-bottom-0 gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row"
class="design-detail js-design-detail fixed-top gl-w-full gl-display-flex gl-justify-content-center gl-flex-direction-column gl-lg-flex-direction-row"
>
<div
class="gl-display-flex gl-overflow-hidden gl-flex-grow-1 gl-flex-direction-column gl-relative"

View File

@ -0,0 +1,55 @@
import { GlButton } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ExperimentHeader from '~/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import * as urlHelpers from '~/lib/utils/url_utility';
import { MOCK_EXPERIMENT } from '../mock_data';
const DELETE_INFO = {
deletePath: '/delete',
deleteConfirmationText: 'MODAL_BODY',
actionPrimaryText: 'Delete!',
modalTitle: 'MODAL_TITLE',
};
describe('~/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue', () => {
let wrapper;
const createWrapper = () => {
wrapper = mountExtended(ExperimentHeader, {
propsData: { title: MOCK_EXPERIMENT.name, deleteInfo: DELETE_INFO },
});
};
const findDeleteButton = () => wrapper.findComponent(DeleteButton);
const findButton = () => wrapper.findComponent(GlButton);
beforeEach(createWrapper);
describe('Delete', () => {
it('shows delete button', () => {
expect(findDeleteButton().exists()).toBe(true);
});
it('passes the right props', () => {
expect(findDeleteButton().props()).toMatchObject(DELETE_INFO);
});
});
describe('CSV download', () => {
it('shows download CSV button', () => {
expect(findDeleteButton().exists()).toBe(true);
});
it('calls the action to download the CSV', () => {
setWindowLocation('https://blah.com/something/1?name=query&orderBy=name');
jest.spyOn(urlHelpers, 'visitUrl').mockImplementation(() => {});
findButton().vm.$emit('click');
expect(urlHelpers.visitUrl).toHaveBeenCalledTimes(1);
expect(urlHelpers.visitUrl).toHaveBeenCalledWith('/something/1.csv?name=query&orderBy=name');
});
});
});

View File

@ -1,10 +1,10 @@
import { GlAlert, GlTableLite, GlLink, GlEmptyState } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import MlExperimentsShow from '~/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue';
import ExperimentHeader from '~/ml/experiment_tracking/routes/experiments/show/components/experiment_header.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import Pagination from '~/vue_shared/components/incubation/pagination.vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import DeleteButton from '~/ml/experiment_tracking/components/delete_button.vue';
import * as urlHelpers from '~/lib/utils/url_utility';
import { MOCK_START_CURSOR, MOCK_PAGE_INFO, MOCK_CANDIDATES, MOCK_EXPERIMENT } from './mock_data';
@ -36,7 +36,7 @@ describe('MlExperimentsShow', () => {
const findTableRows = () => findTable().findAll('tbody > tr');
const findNthTableRow = (idx) => findTableRows().at(idx);
const findColumnInRow = (row, col) => findNthTableRow(row).findAll('td').at(col);
const findDeleteButton = () => wrapper.findComponent(DeleteButton);
const findExperimentHeader = () => wrapper.findComponent(ExperimentHeader);
const hrefInRowAndColumn = (row, col) =>
findColumnInRow(row, col).findComponent(GlLink).attributes().href;
@ -60,8 +60,12 @@ describe('MlExperimentsShow', () => {
expect(findPagination().exists()).toBe(false);
});
it('shows delete button', () => {
expect(findDeleteButton().exists()).toBe(true);
it('shows experiment header', () => {
expect(findExperimentHeader().exists()).toBe(true);
});
it('passes the correct title to experiment header', () => {
expect(findExperimentHeader().props('title')).toBe(MOCK_EXPERIMENT.name);
});
it('does not show table', () => {

View File

@ -121,4 +121,22 @@ describe('ScopeNavigation', () => {
expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(true);
});
});
describe.each`
searchTherm | hasBeenCalled
${null} | ${0}
${'test'} | ${1}
`('fetchSidebarCount', ({ searchTherm, hasBeenCalled }) => {
beforeEach(() => {
createComponent({
urlQuery: {
search: searchTherm,
},
});
});
it('is only called when search term is set', () => {
expect(actionSpies.fetchSidebarCount).toHaveBeenCalledTimes(hasBeenCalled);
});
});
});

View File

@ -25,7 +25,9 @@ jest.mock('~/super_sidebar/utils', () => ({
}));
const focusInputMock = jest.fn();
const persistentLinks = [{ title: 'Explore', link: '/explore', icon: 'compass' }];
const persistentLinks = [
{ title: 'Explore', link: '/explore', icon: 'compass', link_classes: 'persistent-link-class' },
];
const username = 'root';
const projectsPath = 'projectsPath';
const groupsPath = 'groupsPath';
@ -94,8 +96,13 @@ describe('ContextSwitcher component', () => {
it('renders the persistent links', () => {
const navItems = findNavItems();
const firstNavItem = navItems.at(0);
expect(navItems.length).toBe(persistentLinks.length);
expect(navItems.at(0).props('item')).toBe(persistentLinks[0]);
expect(firstNavItem.props('item')).toBe(persistentLinks[0]);
expect(firstNavItem.props('linkClasses')).toEqual({
[persistentLinks[0].link_classes]: persistentLinks[0].link_classes,
});
});
it('passes the placeholder to the search box', () => {

View File

@ -19,11 +19,14 @@ describe('GroupsList component', () => {
const itRendersViewAllItem = () => {
it('renders the "View all..." item', () => {
expect(findViewAllLink().props('item')).toEqual({
const link = findViewAllLink();
expect(link.props('item')).toEqual({
icon: 'group',
link: viewAllLink,
title: s__('Navigation|View all your groups'),
});
expect(link.props('linkClasses')).toEqual({ 'dashboard-shortcuts-groups': true });
});
};

View File

@ -38,6 +38,7 @@ describe('MergeRequestMenu component', () => {
expect(link.attributes('data-track-action')).toBe(extraAttrs['data-track-action']);
expect(link.attributes('data-track-label')).toBe(extraAttrs['data-track-label']);
expect(link.attributes('data-track-property')).toBe(extraAttrs['data-track-property']);
expect(link.attributes('class')).toContain(extraAttrs.class);
});
it('renders item count string in badge', () => {

View File

@ -19,11 +19,14 @@ describe('ProjectsList component', () => {
const itRendersViewAllItem = () => {
it('renders the "View all..." item', () => {
expect(findViewAllLink().props('item')).toEqual({
const link = findViewAllLink();
expect(link.props('item')).toEqual({
icon: 'project',
link: viewAllLink,
title: s__('Navigation|View all your projects'),
});
expect(link.props('linkClasses')).toEqual({ 'dashboard-shortcuts-projects': true });
});
};

View File

@ -67,8 +67,20 @@ describe('SuperSidebar component', () => {
});
it("does not call the context switcher's focusInput method initially", () => {
createWrapper();
expect(focusInputMock).not.toHaveBeenCalled();
});
it('renders hidden shortcut links', () => {
createWrapper();
const [linkAttrs] = sidebarData.shortcut_links;
const link = wrapper.find(`.${linkAttrs.css_class}`);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(linkAttrs.href);
expect(link.attributes('class')).toContain('gl-display-none');
});
});
describe('when opening the context switcher', () => {

View File

@ -73,6 +73,7 @@ describe('UserBar component', () => {
expect(isuesCounter.attributes('data-track-action')).toBe('click_link');
expect(isuesCounter.attributes('data-track-label')).toBe('issues_link');
expect(isuesCounter.attributes('data-track-property')).toBe('nav_core_menu');
expect(isuesCounter.attributes('class')).toContain('dashboard-shortcuts-issues');
});
it('renders merge requests counter', () => {
@ -92,6 +93,7 @@ describe('UserBar component', () => {
expect(todosCounter.attributes('data-track-action')).toBe('click_link');
expect(todosCounter.attributes('data-track-label')).toBe('todos_link');
expect(todosCounter.attributes('data-track-property')).toBe('nav_core_menu');
expect(todosCounter.attributes('class')).toContain('shortcuts-todos');
});
it('renders branding logo', () => {

View File

@ -53,6 +53,7 @@ export const mergeRequestMenuGroup = [
'data-track-action': 'click_link',
'data-track-label': 'merge_requests_assigned',
'data-track-property': 'nav_core_menu',
class: 'dashboard-shortcuts-merge_requests',
},
},
{
@ -63,6 +64,7 @@ export const mergeRequestMenuGroup = [
'data-track-action': 'click_link',
'data-track-label': 'merge_requests_to_review',
'data-track-property': 'nav_core_menu',
class: 'dashboard-shortcuts-review_requests',
},
},
],
@ -104,6 +106,13 @@ export const sidebarData = {
panel_type: 'your_work',
update_pins_url: 'path/to/pins',
stop_impersonation_path: '/admin/impersonation',
shortcut_links: [
{
title: 'Shortcut link',
href: '/shortcut-link',
css_class: 'shortcut-link-class',
},
],
};
export const userMenuMockStatus = {

View File

@ -1,4 +1,5 @@
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import htmlWhatsNewNotification from 'test_fixtures_static/whats_new_notification.html';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { setNotification, getVersionDigest } from '~/whats_new/utils/notification';
@ -12,7 +13,7 @@ describe('~/whats_new/utils/notification', () => {
const getAppEl = () => wrapper.querySelector('.app');
beforeEach(() => {
loadHTMLFixture('static/whats_new_notification.html');
setHTMLFixture(htmlWhatsNewNotification);
wrapper = document.querySelector('.whats-new-notification-fixture-root');
});

View File

@ -139,7 +139,24 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
},
pinned_items: %w[foo bar],
panel_type: panel_type,
update_pins_url: pins_url
update_pins_url: pins_url,
shortcut_links: [
{
title: _('Milestones'),
href: dashboard_milestones_path,
css_class: 'dashboard-shortcuts-milestones'
},
{
title: _('Snippets'),
href: dashboard_snippets_path,
css_class: 'dashboard-shortcuts-snippets'
},
{
title: _('Activity'),
href: activity_dashboard_path,
css_class: 'dashboard-shortcuts-activity'
}
]
})
end
@ -155,7 +172,8 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
extraAttrs: {
'data-track-action': 'click_link',
'data-track-label': 'merge_requests_assigned',
'data-track-property': 'nav_core_menu'
'data-track-property': 'nav_core_menu',
class: 'dashboard-shortcuts-merge_requests'
}
},
{
@ -165,7 +183,8 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do
extraAttrs: {
'data-track-action': 'click_link',
'data-track-label': 'merge_requests_to_review',
'data-track-property': 'nav_core_menu'
'data-track-property': 'nav_core_menu',
class: 'dashboard-shortcuts-review_requests'
}
}
]

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe API::Entities::Clusters::AgentAuthorization do
RSpec.describe API::Entities::Clusters::Agents::Authorizations::CiAccess, feature_category: :kubernetes_management do
subject { described_class.new(authorization).as_json }
shared_examples 'generic authorization' do
@ -16,20 +16,20 @@ RSpec.describe API::Entities::Clusters::AgentAuthorization do
end
context 'project authorization' do
let(:authorization) { create(:agent_project_authorization) }
let(:authorization) { create(:agent_ci_access_project_authorization) }
include_examples 'generic authorization'
end
context 'group authorization' do
let(:authorization) { create(:agent_group_authorization) }
let(:authorization) { create(:agent_ci_access_group_authorization) }
include_examples 'generic authorization'
end
context 'implicit authorization' do
let(:agent) { create(:cluster_agent) }
let(:authorization) { Clusters::Agents::ImplicitAuthorization.new(agent: agent) }
let(:authorization) { Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization.new(agent: agent) }
include_examples 'generic authorization'
end

View File

@ -1,38 +1,41 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rspec-parameterized'
RSpec.describe Gitlab::Slug::Environment do
RSpec.describe Gitlab::Slug::Environment, feature_category: :environment_management do
describe '#generate' do
{
"staging-12345678901234567" => "staging-123456789-q517sa",
"9-staging-123456789012345" => "env-9-staging-123-q517sa",
"staging-1234567890123456" => "staging-1234567890123456",
"staging-1234567890123456-" => "staging-123456789-q517sa",
"production" => "production",
"PRODUCTION" => "production-q517sa",
"review/1-foo" => "review-1-foo-q517sa",
"1-foo" => "env-1-foo-q517sa",
"1/foo" => "env-1-foo-q517sa",
"foo-" => "foo",
"foo--bar" => "foo-bar-q517sa",
"foo**bar" => "foo-bar-q517sa",
"*-foo" => "env-foo-q517sa",
"staging-12345678-" => "staging-12345678",
"staging-12345678-01234567" => "staging-12345678-q517sa",
"" => "env-q517sa",
nil => "env-q517sa"
}.each do |name, matcher|
before do
# ('a' * 64).to_i(16).to_s(36).last(6) gives 'q517sa'
allow(Digest::SHA2).to receive(:hexdigest).with(name).and_return('a' * 64)
end
using RSpec::Parameterized::TableSyntax
it "returns a slug matching #{matcher}, given #{name}" do
slug = described_class.new(name).generate
subject { described_class.new(name).generate }
expect(slug).to match(/\A#{matcher}\z/)
end
before do
# ('a' * 64).to_i(16).to_s(36).last(6) gives 'q517sa'
allow(Digest::SHA2).to receive(:hexdigest).with(name.to_s).and_return('a' * 64)
end
where(:name, :slug) do
"staging-12345678901234567" | "staging-123456789-q517sa"
"9-staging-123456789012345" | "env-9-staging-123-q517sa"
"staging-1234567890123456" | "staging-1234567890123456"
"staging-1234567890123456-" | "staging-123456789-q517sa"
"production" | "production"
"PRODUCTION" | "production-q517sa"
"review/1-foo" | "review-1-foo-q517sa"
"1-foo" | "env-1-foo-q517sa"
"1/foo" | "env-1-foo-q517sa"
"foo-" | "foo"
"foo--bar" | "foo-bar-q517sa"
"foo**bar" | "foo-bar-q517sa"
"*-foo" | "env-foo-q517sa"
"staging-12345678-" | "staging-12345678"
"staging-12345678-01234567" | "staging-12345678-q517sa"
"" | "env-q517sa"
nil | "env-q517sa"
end
with_them do
it { is_expected.to eq(slug) }
end
end
end

Some files were not shown because too many files have changed in this diff Show More