Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
62798ed33c
commit
944a3a7b7e
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
9255b4522513ee91d7fef535137d57deb498bbf8
|
||||
28c0539251cf00a675b78e987ff30b7741425653
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ export default {
|
|||
...mapState(['navigation', 'urlQuery']),
|
||||
},
|
||||
created() {
|
||||
this.fetchSidebarCount();
|
||||
if (this.urlQuery?.search) {
|
||||
this.fetchSidebarCount();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchSidebarCount']),
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -769,6 +769,9 @@ svg {
|
|||
fill: currentColor;
|
||||
}
|
||||
|
||||
.fixed-top {
|
||||
top: calc(var(--system-header-height) + var(--performance-bar-height));
|
||||
}
|
||||
.gl-display-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
d93a103c002a536d11f75256f20e2b8708ec760286f65d89ab5abe446fe629d4
|
||||
|
|
@ -0,0 +1 @@
|
|||
f4472433ac5b74296409a04790d64ed56551358c98428ebb2e5f15d2f3e2db31
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ vulnerability.
|
|||
|
||||

|
||||
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!('-')
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Reference in New Issue