Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4e33606f01
commit
5605efec12
|
|
@ -35,13 +35,24 @@ export default {
|
|||
errorMsg: s__(
|
||||
'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.',
|
||||
),
|
||||
fullAlertDetailsTitle: s__('AlertManagement|Alert details'),
|
||||
overviewTitle: s__('AlertManagement|Overview'),
|
||||
metricsTitle: s__('AlertManagement|Metrics'),
|
||||
reportedAt: s__('AlertManagement|Reported %{when}'),
|
||||
reportedAtWithTool: s__('AlertManagement|Reported %{when} by %{tool}'),
|
||||
},
|
||||
severityLabels: ALERTS_SEVERITY_LABELS,
|
||||
tabsConfig: [
|
||||
{
|
||||
id: 'overview',
|
||||
title: s__('AlertManagement|Overview'),
|
||||
},
|
||||
{
|
||||
id: 'fullDetails',
|
||||
title: s__('AlertManagement|Alert details'),
|
||||
},
|
||||
{
|
||||
id: 'metrics',
|
||||
title: s__('AlertManagement|Metrics'),
|
||||
},
|
||||
],
|
||||
components: {
|
||||
GlBadge,
|
||||
GlAlert,
|
||||
|
|
@ -119,6 +130,18 @@ export default {
|
|||
showErrorMsg() {
|
||||
return this.errored && !this.isErrorDismissed;
|
||||
},
|
||||
activeTab() {
|
||||
return this.$route.params.tabId || this.$options.tabsConfig[0].id;
|
||||
},
|
||||
currentTabIndex: {
|
||||
get() {
|
||||
return this.$options.tabsConfig.findIndex(tab => tab.id === this.activeTab);
|
||||
},
|
||||
set(tabIdx) {
|
||||
const tabId = this.$options.tabsConfig[tabIdx].id;
|
||||
this.$router.replace({ name: 'tab', params: { tabId } });
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.trackPageViews();
|
||||
|
|
@ -257,8 +280,8 @@ export default {
|
|||
>
|
||||
<h2 data-testid="title">{{ alert.title }}</h2>
|
||||
</div>
|
||||
<gl-tabs v-if="alert" data-testid="alertDetailsTabs">
|
||||
<gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle">
|
||||
<gl-tabs v-if="alert" v-model="currentTabIndex" data-testid="alertDetailsTabs">
|
||||
<gl-tab :data-testid="$options.tabsConfig[0].id" :title="$options.tabsConfig[0].title">
|
||||
<div v-if="alert.severity" class="gl-mt-3 gl-mb-5 gl-display-flex">
|
||||
<div class="gl-font-weight-bold gl-w-13 gl-text-right gl-pr-3">
|
||||
{{ s__('AlertManagement|Severity') }}:
|
||||
|
|
@ -309,7 +332,7 @@ export default {
|
|||
</div>
|
||||
</template>
|
||||
</gl-tab>
|
||||
<gl-tab data-testid="fullDetailsTab" :title="$options.i18n.fullAlertDetailsTitle">
|
||||
<gl-tab :data-testid="$options.tabsConfig[1].id" :title="$options.tabsConfig[1].title">
|
||||
<gl-table
|
||||
class="alert-management-details-table"
|
||||
:items="[{ key: 'Value', ...alert }]"
|
||||
|
|
@ -325,7 +348,7 @@ export default {
|
|||
</template>
|
||||
</gl-table>
|
||||
</gl-tab>
|
||||
<gl-tab data-testId="metricsTab" :title="$options.i18n.metricsTitle">
|
||||
<gl-tab :data-testid="$options.tabsConfig[2].id" :title="$options.tabsConfig[2].title">
|
||||
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
|
||||
</gl-tab>
|
||||
</gl-tabs>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import createRouter from './router';
|
||||
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
|
||||
import AlertDetails from './components/alert_details.vue';
|
||||
import sidebarStatusQuery from './graphql/queries/sidebar_status.query.graphql';
|
||||
|
|
@ -10,6 +11,7 @@ Vue.use(VueApollo);
|
|||
export default selector => {
|
||||
const domEl = document.querySelector(selector);
|
||||
const { alertId, projectPath, projectIssuesPath, projectId } = domEl.dataset;
|
||||
const router = createRouter();
|
||||
|
||||
const resolvers = {
|
||||
Mutation: {
|
||||
|
|
@ -54,6 +56,7 @@ export default selector => {
|
|||
components: {
|
||||
AlertDetails,
|
||||
},
|
||||
router,
|
||||
render(createElement) {
|
||||
return createElement('alert-details', {});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
export default function createRouter(base) {
|
||||
return new VueRouter({
|
||||
mode: 'hash',
|
||||
base: joinPaths(gon.relative_url_root || '', base),
|
||||
routes: [{ path: '/:tabId', name: 'tab' }],
|
||||
});
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import ImportStatus from './import_status.vue';
|
||||
import { STATUSES } from '../constants';
|
||||
|
||||
|
|
@ -6,6 +7,7 @@ export default {
|
|||
name: 'ImportedProjectTableRow',
|
||||
components: {
|
||||
ImportStatus,
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
project: {
|
||||
|
|
@ -36,6 +38,7 @@ export default {
|
|||
class="js-provider-link"
|
||||
>
|
||||
{{ project.importSource }}
|
||||
<gl-icon v-if="project.providerLink" name="external-link" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="js-full-path">{{ displayFullPath }}</td>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script>
|
||||
import { GlBadge } from '@gitlab/ui';
|
||||
import { GlIcon, GlBadge } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlBadge,
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
repo: {
|
||||
|
|
@ -19,6 +20,7 @@ export default {
|
|||
<td>
|
||||
<a :href="repo.providerLink" rel="noreferrer noopener" target="_blank">
|
||||
{{ repo.fullName }}
|
||||
<gl-icon v-if="repo.providerLink" name="external-link" />
|
||||
</a>
|
||||
</td>
|
||||
<td></td>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import Select2Select from '~/vue_shared/components/select2_select.vue';
|
||||
import { __ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
|
|
@ -11,6 +12,7 @@ export default {
|
|||
components: {
|
||||
Select2Select,
|
||||
ImportStatus,
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
repo: {
|
||||
|
|
@ -84,6 +86,7 @@ export default {
|
|||
class="js-provider-link"
|
||||
>
|
||||
{{ repo.fullName }}
|
||||
<gl-icon v-if="repo.providerLink" name="external-link" />
|
||||
</a>
|
||||
</td>
|
||||
<td class="d-flex flex-wrap flex-lg-nowrap">
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const fetchPolicies = {
|
|||
};
|
||||
|
||||
export default (resolvers = {}, config = {}) => {
|
||||
let uri = `${gon.relative_url_root}/api/graphql`;
|
||||
let uri = `${gon.relative_url_root || ''}/api/graphql`;
|
||||
|
||||
if (config.baseUrl) {
|
||||
// Prepend baseUrl and ensure that `///` are replaced with `/`
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { PANEL_NEW_PAGE } from '../router/constants';
|
||||
import DuplicateDashboardModal from './duplicate_dashboard_modal.vue';
|
||||
import CreateDashboardModal from './create_dashboard_modal.vue';
|
||||
|
|
@ -38,7 +37,6 @@ export default {
|
|||
GlTooltip: GlTooltipDirective,
|
||||
TrackEvent: TrackEventDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
addingMetricsAvailable: {
|
||||
type: Boolean,
|
||||
|
|
@ -86,7 +84,6 @@ export default {
|
|||
},
|
||||
isMenuItemShown() {
|
||||
return {
|
||||
addPanel: this.glFeatures.metricsDashboardNewPanelPage,
|
||||
duplicateDashboard: this.isOutOfTheBoxDashboard,
|
||||
};
|
||||
},
|
||||
|
|
@ -192,31 +189,29 @@ export default {
|
|||
</gl-modal>
|
||||
</template>
|
||||
|
||||
<template v-if="isMenuItemShown.addPanel">
|
||||
<gl-new-dropdown-item
|
||||
v-if="isMenuItemEnabled.addPanel"
|
||||
data-testid="add-panel-item-enabled"
|
||||
:to="newPanelPageLocation"
|
||||
>
|
||||
{{ $options.i18n.addPanel }}
|
||||
</gl-new-dropdown-item>
|
||||
<gl-new-dropdown-item
|
||||
v-if="isMenuItemEnabled.addPanel"
|
||||
data-testid="add-panel-item-enabled"
|
||||
:to="newPanelPageLocation"
|
||||
>
|
||||
{{ $options.i18n.addPanel }}
|
||||
</gl-new-dropdown-item>
|
||||
|
||||
<!--
|
||||
wrapper for tooltip as button can be `disabled`
|
||||
https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
|
||||
-->
|
||||
<div v-else v-gl-tooltip :title="$options.i18n.addPanelInfo">
|
||||
<gl-new-dropdown-item
|
||||
:alt="$options.i18n.addPanelInfo"
|
||||
:to="newPanelPageLocation"
|
||||
data-testid="add-panel-item-disabled"
|
||||
disabled
|
||||
class="gl-cursor-not-allowed"
|
||||
>
|
||||
<span class="gl-text-gray-400">{{ $options.i18n.addPanel }}</span>
|
||||
</gl-new-dropdown-item>
|
||||
</div>
|
||||
</template>
|
||||
<!--
|
||||
wrapper for tooltip as button can be `disabled`
|
||||
https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
|
||||
-->
|
||||
<div v-else v-gl-tooltip :title="$options.i18n.addPanelInfo">
|
||||
<gl-new-dropdown-item
|
||||
:alt="$options.i18n.addPanelInfo"
|
||||
:to="newPanelPageLocation"
|
||||
data-testid="add-panel-item-disabled"
|
||||
disabled
|
||||
class="gl-cursor-not-allowed"
|
||||
>
|
||||
<span class="gl-text-gray-400">{{ $options.i18n.addPanel }}</span>
|
||||
</gl-new-dropdown-item>
|
||||
</div>
|
||||
|
||||
<gl-new-dropdown-item
|
||||
v-if="isMenuItemEnabled.editDashboard"
|
||||
|
|
@ -230,7 +225,7 @@ export default {
|
|||
<!--
|
||||
wrapper for tooltip as button can be `disabled`
|
||||
https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
|
||||
-->
|
||||
-->
|
||||
<div v-else v-gl-tooltip :title="$options.i18n.editDashboardInfo">
|
||||
<gl-new-dropdown-item
|
||||
:alt="$options.i18n.editDashboardInfo"
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-gl-resize-observer="handleResize" class="gl-my-3 gl-w-full slide-enter-to-element">
|
||||
<div v-gl-resize-observer="handleResize" class="gl-my-3">
|
||||
<delete-alert
|
||||
v-model="deleteAlertType"
|
||||
:garbage-collection-help-page-path="config.garbageCollectionHelpPagePath"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ export default {};
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<transition name="slide">
|
||||
<router-view ref="router-view" />
|
||||
</transition>
|
||||
<router-view ref="router-view" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-100 slide-enter-from-element">
|
||||
<div>
|
||||
<gl-alert
|
||||
v-if="showDeleteAlert"
|
||||
:variant="deleteAlertType"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,16 @@ import SquashBeforeMerge from './squash_before_merge.vue';
|
|||
import CommitsHeader from './commits_header.vue';
|
||||
import CommitEdit from './commit_edit.vue';
|
||||
import CommitMessageDropdown from './commit_message_dropdown.vue';
|
||||
import { AUTO_MERGE_STRATEGIES } from '../../constants';
|
||||
import { AUTO_MERGE_STRATEGIES, DANGER, INFO, WARNING } from '../../constants';
|
||||
|
||||
const PIPELINE_RUNNING_STATE = 'running';
|
||||
const PIPELINE_FAILED_STATE = 'failed';
|
||||
const PIPELINE_PENDING_STATE = 'pending';
|
||||
const PIPELINE_SUCCESS_STATE = 'success';
|
||||
|
||||
const MERGE_FAILED_STATUS = 'failed';
|
||||
const MERGE_SUCCESS_STATUS = 'success';
|
||||
const MERGE_HOOK_VALIDATION_ERROR_STATUS = 'hook_validation_error';
|
||||
|
||||
export default {
|
||||
name: 'ReadyToMerge',
|
||||
|
|
@ -29,6 +38,8 @@ export default {
|
|||
GlSprintf,
|
||||
GlLink,
|
||||
GlDeprecatedButton,
|
||||
MergeTrainHelperText: () =>
|
||||
import('ee_component/vue_merge_request_widget/components/merge_train_helper_text.vue'),
|
||||
MergeImmediatelyConfirmationDialog: () =>
|
||||
import(
|
||||
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
|
||||
|
|
@ -60,35 +71,45 @@ export default {
|
|||
const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
|
||||
|
||||
if ((hasCI && !ciStatus) || this.hasPipelineMustSucceedConflict) {
|
||||
return 'failed';
|
||||
} else if (this.isAutoMergeAvailable) {
|
||||
return 'pending';
|
||||
} else if (!pipeline) {
|
||||
return 'success';
|
||||
} else if (isPipelineFailed) {
|
||||
return 'failed';
|
||||
return PIPELINE_FAILED_STATE;
|
||||
}
|
||||
|
||||
return 'success';
|
||||
if (this.isAutoMergeAvailable) {
|
||||
return PIPELINE_PENDING_STATE;
|
||||
}
|
||||
|
||||
if (pipeline && isPipelineFailed) {
|
||||
return PIPELINE_FAILED_STATE;
|
||||
}
|
||||
|
||||
return PIPELINE_SUCCESS_STATE;
|
||||
},
|
||||
mergeButtonVariant() {
|
||||
if (this.status === 'failed') {
|
||||
return 'danger';
|
||||
} else if (this.status === 'pending') {
|
||||
return 'info';
|
||||
if (this.status === PIPELINE_FAILED_STATE) {
|
||||
return DANGER;
|
||||
}
|
||||
return 'success';
|
||||
|
||||
if (this.status === PIPELINE_PENDING_STATE) {
|
||||
return INFO;
|
||||
}
|
||||
|
||||
return PIPELINE_SUCCESS_STATE;
|
||||
},
|
||||
iconClass() {
|
||||
if (this.shouldRenderMergeTrainHelperText && !this.mr.preventMerge) {
|
||||
return PIPELINE_RUNNING_STATE;
|
||||
}
|
||||
|
||||
if (
|
||||
this.status === 'failed' ||
|
||||
this.status === PIPELINE_FAILED_STATE ||
|
||||
!this.commitMessage.length ||
|
||||
!this.mr.isMergeAllowed ||
|
||||
this.mr.preventMerge
|
||||
) {
|
||||
return 'warning';
|
||||
return WARNING;
|
||||
}
|
||||
return 'success';
|
||||
|
||||
return PIPELINE_SUCCESS_STATE;
|
||||
},
|
||||
mergeButtonText() {
|
||||
if (this.isMergingImmediately) {
|
||||
|
|
@ -167,11 +188,13 @@ export default {
|
|||
.merge(options)
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
|
||||
const hasError =
|
||||
data.status === MERGE_FAILED_STATUS ||
|
||||
data.status === MERGE_HOOK_VALIDATION_ERROR_STATUS;
|
||||
|
||||
if (AUTO_MERGE_STRATEGIES.includes(data.status)) {
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
} else if (data.status === 'success') {
|
||||
} else if (data.status === MERGE_SUCCESS_STATUS) {
|
||||
this.initiateMergePolling();
|
||||
} else if (hasError) {
|
||||
eventHub.$emit('FailedToMerge', data.merge_error);
|
||||
|
|
@ -269,7 +292,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mr-widget-body media">
|
||||
<div class="mr-widget-body media" :class="{ 'gl-pb-3': shouldRenderMergeTrainHelperText }">
|
||||
<status-icon :status="iconClass" />
|
||||
<div class="media-body">
|
||||
<div class="mr-widget-body-controls media space-children">
|
||||
|
|
@ -358,6 +381,7 @@ export default {
|
|||
<div
|
||||
v-if="hasPipelineMustSucceedConflict"
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
data-testid="pipeline-succeed-conflict"
|
||||
>
|
||||
<gl-sprintf :message="pipelineMustSucceedConflictText" />
|
||||
<gl-link
|
||||
|
|
@ -379,6 +403,13 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<merge-train-helper-text
|
||||
v-if="shouldRenderMergeTrainHelperText"
|
||||
:pipeline-id="mr.pipeline.id"
|
||||
:pipeline-link="mr.pipeline.path"
|
||||
:merge-train-length="mr.mergeTrainsCount"
|
||||
:merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
|
||||
/>
|
||||
<template v-if="shouldShowMergeControls">
|
||||
<div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
|
||||
{{ __('Fast-forward merge without a merge commit') }}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { s__ } from '~/locale';
|
|||
export const SUCCESS = 'success';
|
||||
export const WARNING = 'warning';
|
||||
export const DANGER = 'danger';
|
||||
export const INFO = 'info';
|
||||
|
||||
export const WARNING_MESSAGE_CLASS = 'warning_message';
|
||||
export const DANGER_MESSAGE_CLASS = 'danger_message';
|
||||
|
|
|
|||
|
|
@ -243,10 +243,6 @@
|
|||
content: '\f187';
|
||||
}
|
||||
|
||||
.fa-sign-out::before {
|
||||
content: '\f08b';
|
||||
}
|
||||
|
||||
.fa-thumb-tack::before {
|
||||
content: '\f08d';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
|
||||
push_frontend_feature_flag(:prometheus_computed_alerts)
|
||||
push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate)
|
||||
push_frontend_feature_flag(:metrics_dashboard_new_panel_page)
|
||||
end
|
||||
before_action :authorize_read_environment!, except: [:metrics, :additional_metrics, :metrics_dashboard, :metrics_redirect]
|
||||
before_action :authorize_create_environment!, only: [:new, :create]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ module Projects
|
|||
module Metrics
|
||||
module Dashboards
|
||||
class BuilderController < Projects::ApplicationController
|
||||
before_action :ensure_feature_flags
|
||||
before_action :authorize_metrics_dashboard!
|
||||
|
||||
def panel_preview
|
||||
|
|
@ -21,10 +20,6 @@ module Projects
|
|||
|
||||
private
|
||||
|
||||
def ensure_feature_flags
|
||||
render_404 unless Feature.enabled?(:metrics_dashboard_new_panel_page, project)
|
||||
end
|
||||
|
||||
def rendered_panel
|
||||
@panel_preview ||= ::Metrics::Dashboard::PanelPreviewService.new(project, panel_yaml, environment).execute
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,14 +10,9 @@ module Projects
|
|||
before_action do
|
||||
push_frontend_feature_flag(:prometheus_computed_alerts)
|
||||
push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate)
|
||||
push_frontend_feature_flag(:metrics_dashboard_new_panel_page)
|
||||
end
|
||||
|
||||
def show
|
||||
if params[:page].present? && !Feature.enabled?(:metrics_dashboard_new_panel_page, project)
|
||||
return render_404
|
||||
end
|
||||
|
||||
if environment
|
||||
render 'projects/environments/metrics'
|
||||
else
|
||||
|
|
|
|||
|
|
@ -29,12 +29,17 @@ class ProjectRepositoryStorageMove < ApplicationRecord
|
|||
transition scheduled: :started
|
||||
end
|
||||
|
||||
event :finish do
|
||||
transition started: :finished
|
||||
event :finish_replication do
|
||||
transition started: :replicated
|
||||
end
|
||||
|
||||
event :finish_cleanup do
|
||||
transition replicated: :finished
|
||||
end
|
||||
|
||||
event :do_fail do
|
||||
transition [:initial, :scheduled, :started] => :failed
|
||||
transition replicated: :cleanup_failed
|
||||
end
|
||||
|
||||
after_transition initial: :scheduled do |storage_move|
|
||||
|
|
@ -49,7 +54,7 @@ class ProjectRepositoryStorageMove < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
after_transition started: :finished do |storage_move|
|
||||
after_transition started: :replicated do |storage_move|
|
||||
storage_move.project.update_columns(
|
||||
repository_read_only: false,
|
||||
repository_storage: storage_move.destination_storage_name
|
||||
|
|
@ -65,6 +70,8 @@ class ProjectRepositoryStorageMove < ApplicationRecord
|
|||
state :started, value: 3
|
||||
state :finished, value: 4
|
||||
state :failed, value: 5
|
||||
state :replicated, value: 6
|
||||
state :cleanup_failed, value: 7
|
||||
end
|
||||
|
||||
scope :order_created_at_desc, -> { order(created_at: :desc) }
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ module Projects
|
|||
SameFilesystemError = Class.new(Error)
|
||||
|
||||
attr_reader :repository_storage_move
|
||||
delegate :project, :destination_storage_name, to: :repository_storage_move
|
||||
delegate :repository, to: :project
|
||||
delegate :project, :source_storage_name, :destination_storage_name, to: :repository_storage_move
|
||||
|
||||
def initialize(repository_storage_move)
|
||||
@repository_storage_move = repository_storage_move
|
||||
|
|
@ -20,21 +19,22 @@ module Projects
|
|||
repository_storage_move.start!
|
||||
end
|
||||
|
||||
raise SameFilesystemError if same_filesystem?(repository.storage, destination_storage_name)
|
||||
raise SameFilesystemError if same_filesystem?(source_storage_name, destination_storage_name)
|
||||
|
||||
mirror_repositories
|
||||
|
||||
project.transaction do
|
||||
mark_old_paths_for_archive
|
||||
|
||||
repository_storage_move.finish!
|
||||
repository_storage_move.transaction do
|
||||
repository_storage_move.finish_replication!
|
||||
|
||||
project.leave_pool_repository
|
||||
project.track_project_repository
|
||||
end
|
||||
|
||||
remove_old_paths
|
||||
enqueue_housekeeping
|
||||
|
||||
repository_storage_move.finish_cleanup!
|
||||
|
||||
ServiceResponse.success
|
||||
|
||||
rescue StandardError => e
|
||||
|
|
@ -91,36 +91,31 @@ module Projects
|
|||
end
|
||||
end
|
||||
|
||||
def mark_old_paths_for_archive
|
||||
old_repository_storage = project.repository_storage
|
||||
new_project_path = moved_path(project.disk_path)
|
||||
def remove_old_paths
|
||||
Gitlab::Git::Repository.new(
|
||||
source_storage_name,
|
||||
"#{project.disk_path}.git",
|
||||
nil,
|
||||
nil
|
||||
).remove
|
||||
|
||||
# Notice that the block passed to `run_after_commit` will run with `repository_storage_move`
|
||||
# as its context
|
||||
repository_storage_move.run_after_commit do
|
||||
GitlabShellWorker.perform_async(:mv_repository,
|
||||
old_repository_storage,
|
||||
project.disk_path,
|
||||
new_project_path)
|
||||
|
||||
if project.wiki.repository_exists?
|
||||
GitlabShellWorker.perform_async(:mv_repository,
|
||||
old_repository_storage,
|
||||
project.wiki.disk_path,
|
||||
"#{new_project_path}.wiki")
|
||||
end
|
||||
|
||||
if project.design_repository.exists?
|
||||
GitlabShellWorker.perform_async(:mv_repository,
|
||||
old_repository_storage,
|
||||
project.design_repository.disk_path,
|
||||
"#{new_project_path}.design")
|
||||
end
|
||||
if project.wiki.repository_exists?
|
||||
Gitlab::Git::Repository.new(
|
||||
source_storage_name,
|
||||
"#{project.wiki.disk_path}.git",
|
||||
nil,
|
||||
nil
|
||||
).remove
|
||||
end
|
||||
end
|
||||
|
||||
def moved_path(path)
|
||||
"#{path}+#{project.id}+moved+#{Time.current.to_i}"
|
||||
if project.design_repository.exists?
|
||||
Gitlab::Git::Repository.new(
|
||||
source_storage_name,
|
||||
"#{project.design_repository.disk_path}.git",
|
||||
nil,
|
||||
nil
|
||||
).remove
|
||||
end
|
||||
end
|
||||
|
||||
# The underlying FetchInternalRemote call uses a `git fetch` to move data
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
= render 'projects/merge_request_merge_options_settings', project: @project, form: form
|
||||
|
||||
- if Feature.enabled?(:squash_options, @project)
|
||||
- if Feature.enabled?(:squash_options, @project, default_enabled: true)
|
||||
= render 'projects/merge_request_squash_options_settings', form: form
|
||||
|
||||
= render 'projects/merge_request_merge_checks_settings', project: @project, form: form
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Preserve active tab on alert details page reload
|
||||
merge_request: 39369
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Make available new UI for adding a panel to a metrics dashboard
|
||||
merge_request: 39124
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Skip subsequent topology Prometheus queries if timeout occur
|
||||
merge_request: 38293
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove transition animation from the Container Registry UI
|
||||
merge_request: 39337
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove repositories from previous storage when storage move succeeds
|
||||
merge_request: 38547
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add external link icon to list of repositories in importer
|
||||
merge_request: 39442
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -56,6 +56,12 @@ This works because for every path that is present in CE's eager-load/auto-load
|
|||
paths, we add the same `ee/`-prepended path in [`config/application.rb`](https://gitlab.com/gitlab-org/gitlab/blob/925d3d4ebc7a2c72964ce97623ae41b8af12538d/config/application.rb#L42-52).
|
||||
This also applies to views.
|
||||
|
||||
#### Testing EE-only features
|
||||
|
||||
To test an EE class that doesn't exist in CE, create the spec file as you normally
|
||||
would in the `ee/spec` directory, but without the second `ee/` subdirectory.
|
||||
For example, a class `ee/app/models/vulnerability.rb` would have its tests in `ee/spec/models/vulnerability_spec.rb`.
|
||||
|
||||
### EE features based on CE features
|
||||
|
||||
For features that build on existing CE features, write a module in the `EE`
|
||||
|
|
@ -96,6 +102,21 @@ This is also not just applied to models. Here's a list of other examples:
|
|||
- `ee/app/validators/ee/foo_attr_validator.rb`
|
||||
- `ee/app/workers/ee/foo_worker.rb`
|
||||
|
||||
#### Testing EE features based on CE features
|
||||
|
||||
To test an `EE` namespaced module that extends a CE class with EE features,
|
||||
create the spec file as you normally would in the `ee/spec` directory, including the second `ee/` subdirectory.
|
||||
For example, an extension `ee/app/models/ee/user.rb` would have its tests in `ee/app/models/ee/user_spec.rb`.
|
||||
|
||||
In the `RSpec.describe` call, use the CE class name where the EE module would be used.
|
||||
For example, in `ee/app/models/ee/user_spec.rb`, the test would start with:
|
||||
|
||||
```ruby
|
||||
RSpec.describe User do
|
||||
describe 'ee feature added through extension'
|
||||
end
|
||||
```
|
||||
|
||||
#### Overriding CE methods
|
||||
|
||||
To override a method present in the CE codebase, use `prepend`. It
|
||||
|
|
|
|||
|
|
@ -379,6 +379,10 @@ Ensure you comply with the [Changelog entries guide](../changelog.md).
|
|||
|
||||
On GitLab.com, we have DangerBot setup to monitor Telemetry related files and DangerBot will recommend a Telemetry review. Mention `@gitlab-org/growth/telemetry/engineers` in your MR for a review.
|
||||
|
||||
### 9. Verify your metric
|
||||
|
||||
On GitLab.com, the Product Analytics team regularly monitors Usage Ping. They may alert you that your metrics need further optimization to run quicker and with greater success. You may also use the [Usage Ping QA dashboard](https://app.periscopedata.com/app/gitlab/632033/Usage-Ping-QA) to check how well your metric performs. The dashboard allows filtering by GitLab version, by "Self-managed" & "Saas" and shows you how many failures have occurred for each metric. Whenever you notice a high failure rate, you may re-optimize your metric.
|
||||
|
||||
### Optional: Test Prometheus based Usage Ping
|
||||
|
||||
If the data submitted includes metrics [queried from Prometheus](#prometheus-queries) that you would like to inspect and verify,
|
||||
|
|
|
|||
|
|
@ -246,10 +246,7 @@ On the EC2 dashboard, look for Load Balancer in the left navigation bar:
|
|||
1. For **Ping Protocol**, select HTTP.
|
||||
1. For **Ping Port**, enter 80.
|
||||
1. For **Ping Path**, enter `/users/sign_in`. (We use `/users/sign_in` as it's a public endpoint that does
|
||||
not require authorization.)
|
||||
|
||||
NOTE: **Note:**
|
||||
When booting a fresh GitLab instance for the first time, GitLab redirects you to `/users/password/` to change the admin password. Temporarily change the health check to this URL (or to the TCP protocol) and change it back to `/users/sign_in` after setting the admin password.
|
||||
not require authentication.)
|
||||
1. Keep the default **Advanced Details** or adjust them according to your needs.
|
||||
1. Click **Add EC2 Instances** - don't add anything as we will create an Auto Scaling Group later to manage instances for us.
|
||||
1. Click **Add Tags** and add any tags you need.
|
||||
|
|
@ -646,6 +643,13 @@ to eliminate the need for NFS to support GitLab Pages.
|
|||
|
||||
That concludes the configuration changes for our GitLab instance. Next, we'll create a custom AMI based on this instance to use for our launch configuration and auto scaling group.
|
||||
|
||||
### Log in for the first time
|
||||
|
||||
Using the domain name you used when setting up [DNS for the load balancer](#configure-dns-for-load-balancer), you should now be able to visit GitLab in your browser. You will be asked to set up a password
|
||||
for the `root` user which has admin privileges on the GitLab instance. This password will be stored in the database.
|
||||
|
||||
When our [auto scaling group](#create-an-auto-scaling-group) spins up new instances, we'll be able to log in with username `root` and the newly created password.
|
||||
|
||||
### Create custom AMI
|
||||
|
||||
On the EC2 dashboard:
|
||||
|
|
@ -700,13 +704,6 @@ As the auto scaling group is created, you'll see your new instances spinning up
|
|||
|
||||
Since our instances are created by the auto scaling group, go back to your instances and terminate the [instance we created manually above](#install-gitlab). We only needed this instance to create our custom AMI.
|
||||
|
||||
### Log in for the first time
|
||||
|
||||
Using the domain name you used when setting up [DNS for the load balancer](#configure-dns-for-load-balancer), you should now be able to visit GitLab in your browser. The very first time you will be asked to set up a password
|
||||
for the `root` user which has admin privileges on the GitLab instance.
|
||||
|
||||
After you set it up, login with username `root` and the newly created password.
|
||||
|
||||
## Health check and monitoring with Prometheus
|
||||
|
||||
Apart from Amazon's Cloudwatch which you can enable on various services,
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
|
|
@ -188,7 +188,7 @@ A few notes:
|
|||
cycles, calculate their median time and the result is what the dashboard of
|
||||
Value Stream Analytics is showing.
|
||||
|
||||
## Customizable Value Stream Analytics
|
||||
## Customizable Value Stream Analytics **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12196) in GitLab 12.9.
|
||||
|
||||
|
|
@ -196,8 +196,7 @@ The default stages are designed to work straight out of the box, but they might
|
|||
all teams. Different teams use different approaches to building software, so some teams might want
|
||||
to customize their Value Stream Analytics.
|
||||
|
||||
GitLab allows users to hide default stages and create custom stages that align better to their
|
||||
development workflow.
|
||||
GitLab allows users to create multiple value streams, hide default stages and create custom stages that align better to their development workflow.
|
||||
|
||||
NOTE: **Note:**
|
||||
Customizability is [only available for group-level](https://gitlab.com/gitlab-org/gitlab/-/issues/35823#note_272558950) Value Stream Analytics.
|
||||
|
|
@ -295,6 +294,34 @@ To recover a default stage that was previously hidden:
|
|||
1. In the top right corner open the **Recover hidden stage** dropdown.
|
||||
1. Select a stage.
|
||||
|
||||
### Creating a value stream
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221202) in GitLab 13.3
|
||||
|
||||
A default value stream is readily available for each group. You can create additional value streams based on the different areas of work that you would like to measure.
|
||||
|
||||
Once created, a new value stream includes the [seven stages](#overview) that follow
|
||||
[GitLab workflow](../../topics/gitlab_flow.md)
|
||||
best practices. You can customize this flow by adding, hiding or re-ordering stages.
|
||||
|
||||
To create a value stream:
|
||||
|
||||
1. Navigate to your group's **Analytics > Value Stream**.
|
||||
1. Click the Value stream dropdown and select **Create new Value Stream**
|
||||
1. Fill in a name for the new Value Stream
|
||||
1. Click the **Create Value Stream** button.
|
||||
|
||||

|
||||
|
||||
### Disabling custom value streams
|
||||
|
||||
Custom value streams are enabled by default. If you have a self-managed instance, an
|
||||
administrator can open a Rails console and disable them with the following command:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:value_stream_analytics_create_multiple_value_streams)
|
||||
```
|
||||
|
||||
## Days to completion chart
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21631) in GitLab 12.6.
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def self.lint_creates_pipeline_with_dry_run?(project)
|
||||
::Feature.enabled?(:ci_lint_creates_pipeline_with_dry_run, project)
|
||||
::Feature.enabled?(:ci_lint_creates_pipeline_with_dry_run, project, default_enabled: true)
|
||||
end
|
||||
|
||||
def self.reset_ci_minutes_for_all_namespaces?
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ module Gitlab
|
|||
'registry' => 'registry'
|
||||
}.freeze
|
||||
|
||||
# If these errors occur, all subsequent queries are likely to fail for the same error
|
||||
TIMEOUT_ERRORS = [Errno::ETIMEDOUT, Net::OpenTimeout, Net::ReadTimeout].freeze
|
||||
|
||||
CollectionFailure = Struct.new(:query, :error) do
|
||||
def to_h
|
||||
{ query => error }
|
||||
|
|
@ -158,6 +161,11 @@ module Gitlab
|
|||
end
|
||||
|
||||
def query_safely(query, query_name, fallback:)
|
||||
if timeout_error_exists?
|
||||
@failures << CollectionFailure.new(query_name, 'timeout_cancellation')
|
||||
return fallback
|
||||
end
|
||||
|
||||
result = yield query
|
||||
|
||||
return result if result.present?
|
||||
|
|
@ -169,6 +177,14 @@ module Gitlab
|
|||
fallback
|
||||
end
|
||||
|
||||
def timeout_error_exists?
|
||||
timeout_error_names = TIMEOUT_ERRORS.map(&:to_s).to_set
|
||||
|
||||
@failures.any? do |failure|
|
||||
timeout_error_names.include?(failure.error)
|
||||
end
|
||||
end
|
||||
|
||||
def topology_node_services(instance, all_process_counts, all_process_memory, all_server_types)
|
||||
# returns all node service data grouped by service name as the key
|
||||
instance_service_data =
|
||||
|
|
|
|||
|
|
@ -29333,6 +29333,12 @@ msgstr ""
|
|||
msgid "mrWidget|There are merge conflicts"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|This action will add the merge request to the merge train when pipeline %{pipelineLink} succeeds."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|This action will start a merge train when pipeline %{pipelineLink} succeeds."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29342,12 +29348,6 @@ msgstr ""
|
|||
msgid "mrWidget|This merge request is in the process of being merged"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds."
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|This project is archived, write access has been disabled"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.158.0",
|
||||
"@gitlab/ui": "18.6.2",
|
||||
"@gitlab/ui": "18.7.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-1",
|
||||
"@sentry/browser": "^5.10.2",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ FactoryBot.define do
|
|||
state { ProjectRepositoryStorageMove.state_machines[:state].states[:started].value }
|
||||
end
|
||||
|
||||
trait :replicated do
|
||||
state { ProjectRepositoryStorageMove.state_machines[:state].states[:replicated].value }
|
||||
end
|
||||
|
||||
trait :finished do
|
||||
state { ProjectRepositoryStorageMove.state_machines[:state].states[:finished].value }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ RSpec.describe 'Mini Pipeline Graph in Commit View', :js do
|
|||
build.run
|
||||
visit project_commit_path(project, project.commit.id)
|
||||
|
||||
wait_for_all_requests
|
||||
|
||||
expect(page).to have_selector('.mr-widget-pipeline-graph')
|
||||
|
||||
first('.mini-pipeline-graph-dropdown-toggle').click
|
||||
|
|
|
|||
|
|
@ -235,15 +235,18 @@ RSpec.describe TodosFinder do
|
|||
|
||||
context 'when filtering by target id' do
|
||||
it 'returns the expected todos for the target' do
|
||||
todos = finder.new(user, { target_id: issue.id }).execute
|
||||
todos = finder.new(user, { type: 'Issue', target_id: issue.id }).execute
|
||||
|
||||
expect(todos).to match_array([todo1])
|
||||
end
|
||||
|
||||
it 'returns the expected todos for multiple target ids' do
|
||||
todos = finder.new(user, { target_id: [issue.id, merge_request.id] }).execute
|
||||
another_issue = create(:issue, project: project)
|
||||
todo3 = create(:todo, user: user, project: project, target: another_issue)
|
||||
|
||||
expect(todos).to match_array([todo1, todo2])
|
||||
todos = finder.new(user, { type: 'Issue', target_id: [issue.id, another_issue.id] }).execute
|
||||
|
||||
expect(todos).to match_array([todo1, todo3])
|
||||
end
|
||||
|
||||
it 'returns the expected todos for empty target id collection' do
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ describe('AlertDetails', () => {
|
|||
const projectPath = 'root/alerts';
|
||||
const projectIssuesPath = 'root/alerts/-/issues';
|
||||
const projectId = '1';
|
||||
const $router = { replace: jest.fn() };
|
||||
|
||||
const findDetailsTable = () => wrapper.find(GlTable);
|
||||
|
||||
|
|
@ -44,6 +45,8 @@ describe('AlertDetails', () => {
|
|||
sidebarStatus: {},
|
||||
},
|
||||
},
|
||||
$router,
|
||||
$route: { params: {} },
|
||||
},
|
||||
stubs,
|
||||
});
|
||||
|
|
@ -81,11 +84,11 @@ describe('AlertDetails', () => {
|
|||
});
|
||||
|
||||
it('renders a tab with overview information', () => {
|
||||
expect(wrapper.find('[data-testid="overviewTab"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="overview"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a tab with full alert information', () => {
|
||||
expect(wrapper.find('[data-testid="fullDetailsTab"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="fullDetails"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders severity', () => {
|
||||
|
|
@ -191,7 +194,7 @@ describe('AlertDetails', () => {
|
|||
mountComponent({ data: { alert: mockAlert } });
|
||||
});
|
||||
it('should display a table of raw alert details data', () => {
|
||||
wrapper.find('[data-testid="fullDetailsTab"]').trigger('click');
|
||||
wrapper.find('[data-testid="fullDetails"]').trigger('click');
|
||||
expect(findDetailsTable().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -252,6 +255,22 @@ describe('AlertDetails', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tab navigation', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ data: { alert: mockAlert } });
|
||||
});
|
||||
|
||||
it.each`
|
||||
index | tabId
|
||||
${0} | ${'overview'}
|
||||
${1} | ${'fullDetails'}
|
||||
${2} | ${'metrics'}
|
||||
`('will navigate to the correct tab via $tabId', ({ index, tabId }) => {
|
||||
wrapper.setData({ currentTabIndex: index });
|
||||
expect($router.replace).toHaveBeenCalledWith({ name: 'tab', params: { tabId } });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Snowplow tracking', () => {
|
||||
|
|
@ -42,9 +42,6 @@ describe('Actions menu', () => {
|
|||
const createShallowWrapper = (props = {}, options = {}) => {
|
||||
wrapper = shallowMount(ActionsMenu, {
|
||||
propsData: { ...dashboardActionsMenuProps, ...props },
|
||||
provide: {
|
||||
glFeatures: { metricsDashboardNewPanelPage: true },
|
||||
},
|
||||
store,
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -402,28 +402,61 @@ RSpec.describe Gitlab::UsageData::Topology do
|
|||
end
|
||||
|
||||
context 'and an error is raised when querying Prometheus' do
|
||||
it 'returns empty result with failures' do
|
||||
expect_prometheus_api_to receive(:query)
|
||||
.at_least(:once)
|
||||
.and_raise(Gitlab::PrometheusClient::ConnectionError)
|
||||
context 'without timeout failures' do
|
||||
it 'returns empty result and executes subsequent queries as usual' do
|
||||
expect_prometheus_api_to receive(:query)
|
||||
.at_least(:once)
|
||||
.and_raise(Gitlab::PrometheusClient::ConnectionError)
|
||||
|
||||
expect(subject[:topology]).to eq({
|
||||
duration_s: 0,
|
||||
failures: [
|
||||
{ 'app_requests' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_memory' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_memory_utilization' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_cpus' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_cpu_utilization' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_uname_info' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_rss' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_uss' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_pss' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_process_count' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_workers' => 'Gitlab::PrometheusClient::ConnectionError' }
|
||||
],
|
||||
nodes: []
|
||||
})
|
||||
expect(subject[:topology]).to eq({
|
||||
duration_s: 0,
|
||||
failures: [
|
||||
{ 'app_requests' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_memory' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_memory_utilization' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_cpus' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_cpu_utilization' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'node_uname_info' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_rss' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_uss' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_pss' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_process_count' => 'Gitlab::PrometheusClient::ConnectionError' },
|
||||
{ 'service_workers' => 'Gitlab::PrometheusClient::ConnectionError' }
|
||||
],
|
||||
nodes: []
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with timeout failures' do
|
||||
where(:exception) do
|
||||
described_class::TIMEOUT_ERRORS
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'returns empty result and cancelled subsequent queries' do
|
||||
expect_prometheus_api_to receive(:query)
|
||||
.and_raise(exception)
|
||||
|
||||
expect(subject[:topology]).to eq({
|
||||
duration_s: 0,
|
||||
failures: [
|
||||
{ 'app_requests' => exception.to_s },
|
||||
{ 'node_memory' => 'timeout_cancellation' },
|
||||
{ 'node_memory_utilization' => 'timeout_cancellation' },
|
||||
{ 'node_cpus' => 'timeout_cancellation' },
|
||||
{ 'node_cpu_utilization' => 'timeout_cancellation' },
|
||||
{ 'node_uname_info' => 'timeout_cancellation' },
|
||||
{ 'service_rss' => 'timeout_cancellation' },
|
||||
{ 'service_uss' => 'timeout_cancellation' },
|
||||
{ 'service_pss' => 'timeout_cancellation' },
|
||||
{ 'service_process_count' => 'timeout_cancellation' },
|
||||
{ 'service_workers' => 'timeout_cancellation' }
|
||||
],
|
||||
nodes: []
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -113,9 +113,10 @@ RSpec.describe Member do
|
|||
end
|
||||
|
||||
describe 'Scopes & finders' do
|
||||
before do
|
||||
project = create(:project, :public)
|
||||
group = create(:group)
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
before_all do
|
||||
@owner_user = create(:user).tap { |u| group.add_owner(u) }
|
||||
@owner = group.members.find_by(user_id: @owner_user.id)
|
||||
|
||||
|
|
@ -252,9 +253,9 @@ RSpec.describe Member do
|
|||
describe '.add_user' do
|
||||
%w[project group].each do |source_type|
|
||||
context "when source is a #{source_type}" do
|
||||
let!(:source) { create(source_type, :public) }
|
||||
let!(:user) { create(:user) }
|
||||
let!(:admin) { create(:admin) }
|
||||
let_it_be(:source, reload: true) { create(source_type, :public) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
|
||||
it 'returns a <Source>Member object' do
|
||||
member = described_class.add_user(source, user, :maintainer)
|
||||
|
|
@ -322,7 +323,7 @@ RSpec.describe Member do
|
|||
it 'adds the user as a member' do
|
||||
expect(source.users).not_to include(user)
|
||||
|
||||
described_class.add_user(source, 42, :maintainer)
|
||||
described_class.add_user(source, non_existing_record_id, :maintainer)
|
||||
|
||||
expect(source.users.reload).not_to include(user)
|
||||
end
|
||||
|
|
@ -482,10 +483,10 @@ RSpec.describe Member do
|
|||
describe '.add_users' do
|
||||
%w[project group].each do |source_type|
|
||||
context "when source is a #{source_type}" do
|
||||
let!(:source) { create(source_type, :public) }
|
||||
let!(:admin) { create(:admin) }
|
||||
let(:user1) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
let_it_be(:source) { create(source_type, :public) }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
let_it_be(:user1) { create(:user) }
|
||||
let_it_be(:user2) { create(:user) }
|
||||
|
||||
it 'returns a <Source>Member objects' do
|
||||
members = described_class.add_users(source, [user1, user2], :maintainer)
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ RSpec.describe ProjectRepositoryStorageMove, type: :model do
|
|||
context 'when started' do
|
||||
subject(:storage_move) { create(:project_repository_storage_move, :started, project: project, destination_storage_name: 'test_second_storage') }
|
||||
|
||||
context 'and transits to finished' do
|
||||
context 'and transits to replicated' do
|
||||
it 'sets the repository storage and marks the project as writable' do
|
||||
storage_move.finish!
|
||||
storage_move.finish_replication!
|
||||
|
||||
expect(project.repository_storage).to eq('test_second_storage')
|
||||
expect(project).not_to be_repository_read_only
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::ComposerPackages do
|
||||
include PackagesManagerApiSpecHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group, reload: true) { create(:group, :public) }
|
||||
|
|
@ -224,7 +224,7 @@ RSpec.describe API::ComposerPackages do
|
|||
end
|
||||
|
||||
context 'with no tag or branch params' do
|
||||
let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
|
||||
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'process Composer api request', :developer, :bad_request
|
||||
end
|
||||
|
|
@ -238,7 +238,7 @@ RSpec.describe API::ComposerPackages do
|
|||
|
||||
context 'with a non existing tag' do
|
||||
let(:params) { { tag: 'non-existing-tag' } }
|
||||
let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
|
||||
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'process Composer api request', :developer, :not_found
|
||||
end
|
||||
|
|
@ -253,7 +253,7 @@ RSpec.describe API::ComposerPackages do
|
|||
|
||||
context 'with a non existing branch' do
|
||||
let(:params) { { branch: 'non-existing-branch' } }
|
||||
let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
|
||||
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'process Composer api request', :developer, :not_found
|
||||
end
|
||||
|
|
@ -311,7 +311,7 @@ RSpec.describe API::ComposerPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe API::ConanPackages do
|
||||
include WorkhorseHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
include PackagesManagerApiSpecHelpers
|
||||
|
||||
let(:package) { create(:conan_package) }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe API::GoProxy do
|
||||
include PackagesManagerApiSpecHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
|
||||
let_it_be(:user) { create :user }
|
||||
let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
|
||||
|
|
@ -387,7 +388,7 @@ RSpec.describe API::GoProxy do
|
|||
end
|
||||
|
||||
it 'returns ok with a personal access token and basic authentication' do
|
||||
get_resource(headers: build_basic_auth_header(user.username, pa_token.token))
|
||||
get_resource(headers: basic_auth_header(user.username, pa_token.token))
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe API::NpmPackages do
|
||||
include PackagesManagerApiSpecHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
|
|
@ -204,7 +204,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
|
|
@ -264,7 +264,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -325,7 +325,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -381,7 +381,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -436,7 +436,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -499,7 +499,7 @@ RSpec.describe API::NugetPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
RSpec.describe API::PypiPackages do
|
||||
include WorkhorseHelpers
|
||||
include PackagesManagerApiSpecHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :public) }
|
||||
|
|
@ -43,7 +44,7 @@ RSpec.describe API::PypiPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -94,7 +95,7 @@ RSpec.describe API::PypiPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
|
|
@ -157,7 +158,7 @@ RSpec.describe API::PypiPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
|
|
@ -170,7 +171,7 @@ RSpec.describe API::PypiPackages do
|
|||
|
||||
context 'with an invalid package' do
|
||||
let(:token) { personal_access_token.token }
|
||||
let(:user_headers) { build_basic_auth_header(user.username, token) }
|
||||
let(:user_headers) { basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
|
|
@ -220,7 +221,7 @@ RSpec.describe API::PypiPackages do
|
|||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -233,14 +234,14 @@ RSpec.describe API::PypiPackages do
|
|||
end
|
||||
|
||||
context 'with deploy token headers' do
|
||||
let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
|
||||
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
|
||||
|
||||
context 'valid token' do
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
context 'invalid token' do
|
||||
let(:headers) { build_basic_auth_header('foo', 'bar') }
|
||||
let(:headers) { basic_auth_header('foo', 'bar') }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,10 +49,6 @@ RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do
|
|||
|
||||
describe 'POST /:namespace/:project/-/metrics/dashboards/builder' do
|
||||
context 'as anonymous user' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: true)
|
||||
end
|
||||
|
||||
it 'redirects user to sign in page' do
|
||||
send_request
|
||||
|
||||
|
|
@ -62,7 +58,6 @@ RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do
|
|||
|
||||
context 'as user with guest access' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: true)
|
||||
project.add_guest(user)
|
||||
login_as(user)
|
||||
end
|
||||
|
|
@ -80,48 +75,30 @@ RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do
|
|||
login_as(user)
|
||||
end
|
||||
|
||||
context 'metrics_dashboard_new_panel_page is enabled' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: true)
|
||||
end
|
||||
context 'valid yaml panel is supplied' do
|
||||
it 'returns success' do
|
||||
send_request(panel_yaml: valid_panel_yml)
|
||||
|
||||
context 'valid yaml panel is supplied' do
|
||||
it 'returns success' do
|
||||
send_request(panel_yaml: valid_panel_yml)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include('title' => 'Super Chart A1', 'type' => 'area-chart')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid yaml panel is supplied' do
|
||||
it 'returns unprocessable entity' do
|
||||
send_request(panel_yaml: invalid_panel_yml)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response['message']).to eq('Each "panel" must define an array :metrics')
|
||||
end
|
||||
end
|
||||
|
||||
context 'invalid panel_yaml is not a yaml string' do
|
||||
it 'returns unprocessable entity' do
|
||||
send_request(panel_yaml: 1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response['message']).to eq('Invalid configuration format')
|
||||
end
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include('title' => 'Super Chart A1', 'type' => 'area-chart')
|
||||
end
|
||||
end
|
||||
|
||||
context 'metrics_dashboard_new_panel_page is disabled' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: false)
|
||||
context 'invalid yaml panel is supplied' do
|
||||
it 'returns unprocessable entity' do
|
||||
send_request(panel_yaml: invalid_panel_yml)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response['message']).to eq('Each "panel" must define an array :metrics')
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns not found' do
|
||||
send_request
|
||||
context 'invalid panel_yaml is not a yaml string' do
|
||||
it 'returns unprocessable entity' do
|
||||
send_request(panel_yaml: 1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response['message']).to eq('Invalid configuration format')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -96,26 +96,26 @@ RSpec.describe 'metrics dashboard page' do
|
|||
end
|
||||
|
||||
describe 'GET :/namespace/:project/-/metrics/:page' do
|
||||
it 'returns 200 with path param page and feature flag enabled' do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: true)
|
||||
|
||||
it 'returns 200 with path param page' do
|
||||
# send_request(page: 'panel/new') cannot be used because it encodes '/'
|
||||
get "/#{project.namespace.to_param}/#{project.to_param}/-/metrics/panel/new"
|
||||
get "#{dashboard_route}/panel/new"
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'returns 404 with path param page and feature flag disabled' do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: false)
|
||||
|
||||
it 'returns 200 with dashboard and path param page' do
|
||||
# send_request(page: 'panel/new') cannot be used because it encodes '/'
|
||||
get "/#{project.namespace.to_param}/#{project.to_param}/-/metrics/panel/new"
|
||||
get "#{dashboard_route(dashboard_path: 'dashboard.yml')}/panel/new"
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
def send_request(params = {})
|
||||
get namespace_project_metrics_dashboard_path(namespace_id: project.namespace, project_id: project, **params)
|
||||
get dashboard_route(params)
|
||||
end
|
||||
|
||||
def dashboard_route(params = {})
|
||||
namespace_project_metrics_dashboard_path(namespace_id: project.namespace, project_id: project, **params)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
|
|||
let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, project: project, destination_storage_name: destination) }
|
||||
let!(:checksum) { project.repository.checksum }
|
||||
let(:project_repository_double) { double(:repository) }
|
||||
let(:original_project_repository_double) { double(:repository) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
|
||||
|
|
@ -29,6 +30,9 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
|
|||
allow(Gitlab::Git::Repository).to receive(:new)
|
||||
.with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path)
|
||||
.and_return(project_repository_double)
|
||||
allow(Gitlab::Git::Repository).to receive(:new)
|
||||
.with('default', project.repository.raw.relative_path, nil, nil)
|
||||
.and_return(original_project_repository_double)
|
||||
end
|
||||
|
||||
context 'when the move succeeds' do
|
||||
|
|
@ -41,8 +45,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
|
|||
.with(project.repository.raw)
|
||||
expect(project_repository_double).to receive(:checksum)
|
||||
.and_return(checksum)
|
||||
expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything)
|
||||
.and_call_original
|
||||
expect(original_project_repository_double).to receive(:remove)
|
||||
|
||||
result = subject.execute
|
||||
project.reload
|
||||
|
|
@ -74,13 +77,29 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
|
|||
expect(project_repository_double).to receive(:replicate)
|
||||
.with(project.repository.raw)
|
||||
.and_raise(Gitlab::Git::CommandError)
|
||||
expect(GitlabShellWorker).not_to receive(:perform_async)
|
||||
|
||||
result = subject.execute
|
||||
|
||||
expect(result).to be_error
|
||||
expect(project).not_to be_repository_read_only
|
||||
expect(project.repository_storage).to eq('default')
|
||||
expect(repository_storage_move).to be_failed
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the cleanup fails' do
|
||||
it 'sets the correct state' do
|
||||
expect(project_repository_double).to receive(:replicate)
|
||||
.with(project.repository.raw)
|
||||
expect(project_repository_double).to receive(:checksum)
|
||||
.and_return(checksum)
|
||||
expect(original_project_repository_double).to receive(:remove)
|
||||
.and_raise(Gitlab::Git::CommandError)
|
||||
|
||||
result = subject.execute
|
||||
|
||||
expect(result).to be_error
|
||||
expect(repository_storage_move).to be_cleanup_failed
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -93,7 +112,6 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
|
|||
.with(project.repository.raw)
|
||||
expect(project_repository_double).to receive(:checksum)
|
||||
.and_return('not matching checksum')
|
||||
expect(GitlabShellWorker).not_to receive(:perform_async)
|
||||
|
||||
result = subject.execute
|
||||
|
||||
|
|
@ -114,6 +132,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
|
|||
.with(project.repository.raw)
|
||||
expect(project_repository_double).to receive(:checksum)
|
||||
.and_return(checksum)
|
||||
expect(original_project_repository_double).to receive(:remove)
|
||||
|
||||
result = subject.execute
|
||||
project.reload
|
||||
|
|
|
|||
|
|
@ -15,12 +15,15 @@ module HttpBasicAuthHelpers
|
|||
basic_auth_header(client.uid, client.secret)
|
||||
end
|
||||
|
||||
def build_auth_headers(value)
|
||||
{ 'HTTP_AUTHORIZATION' => value }
|
||||
end
|
||||
|
||||
def build_token_auth_header(token)
|
||||
build_auth_headers("Bearer #{token}")
|
||||
end
|
||||
|
||||
def basic_auth_header(username, password)
|
||||
{
|
||||
'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(
|
||||
username,
|
||||
password
|
||||
)
|
||||
}
|
||||
build_auth_headers(ActionController::HttpAuthentication::Basic.encode_credentials(username, password))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PackagesManagerApiSpecHelpers
|
||||
def build_auth_headers(value)
|
||||
{ 'HTTP_AUTHORIZATION' => value }
|
||||
end
|
||||
|
||||
def build_basic_auth_header(username, password)
|
||||
build_auth_headers(ActionController::HttpAuthentication::Basic.encode_credentials(username, password))
|
||||
end
|
||||
|
||||
def build_token_auth_header(token)
|
||||
build_auth_headers("Bearer #{token}")
|
||||
end
|
||||
|
||||
def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
|
||||
JSONWebToken::HMACToken.new(secret).tap do |jwt|
|
||||
jwt['access_token'] = personal_access_token.id
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ end
|
|||
|
||||
RSpec.shared_context 'Composer auth headers' do |user_role, user_token|
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
end
|
||||
|
||||
RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token|
|
||||
|
|
@ -118,7 +118,7 @@ RSpec.shared_examples 'rejects Composer access with unknown group id' do
|
|||
end
|
||||
|
||||
context 'as authenticated user' do
|
||||
subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
|
||||
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'process Composer api request', :anonymous, :not_found
|
||||
end
|
||||
|
|
@ -134,7 +134,7 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
|
|||
end
|
||||
|
||||
context 'as authenticated user' do
|
||||
subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
|
||||
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'process Composer api request', :anonymous, :not_found
|
||||
end
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, sta
|
|||
|
||||
context 'with a request that bypassed gitlab-workhorse' do
|
||||
let(:headers) do
|
||||
build_basic_auth_header(user.username, personal_access_token.token)
|
||||
basic_auth_header(user.username, personal_access_token.token)
|
||||
.merge(workhorse_header)
|
||||
.tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
|
||||
end
|
||||
|
|
@ -401,7 +401,7 @@ RSpec.shared_examples 'rejects nuget access with unknown project id' do
|
|||
end
|
||||
|
||||
context 'as authenticated user' do
|
||||
subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
|
||||
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
RSpec.shared_examples 'deploy token for package GET requests' do
|
||||
context 'with deploy token headers' do
|
||||
let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
|
||||
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ RSpec.shared_examples 'deploy token for package GET requests' do
|
|||
end
|
||||
|
||||
context 'invalid token' do
|
||||
let(:headers) { build_basic_auth_header(deploy_token.username, 'bar') }
|
||||
let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
|
||||
|
||||
it_behaves_like 'returning response status', :unauthorized
|
||||
end
|
||||
|
|
@ -24,7 +24,7 @@ end
|
|||
|
||||
RSpec.shared_examples 'deploy token for package uploads' do
|
||||
context 'with deploy token headers' do
|
||||
let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
|
||||
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
|
||||
|
|
@ -35,7 +35,7 @@ RSpec.shared_examples 'deploy token for package uploads' do
|
|||
end
|
||||
|
||||
context 'invalid token' do
|
||||
let(:headers) { build_basic_auth_header(deploy_token.username, 'bar').merge(workhorse_header) }
|
||||
let(:headers) { basic_auth_header(deploy_token.username, 'bar').merge(workhorse_header) }
|
||||
|
||||
it_behaves_like 'returning response status', :unauthorized
|
||||
end
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ RSpec.shared_examples 'rejects PyPI access with unknown project id' do
|
|||
end
|
||||
|
||||
context 'as authenticated user' do
|
||||
subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
|
||||
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
|
||||
|
||||
it_behaves_like 'process PyPi api request', :anonymous, :not_found
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
RSpec.shared_examples 'moves repository to another storage' do |repository_type|
|
||||
let(:project_repository_double) { double(:repository) }
|
||||
let(:original_project_repository_double) { double(:repository) }
|
||||
let!(:project_repository_checksum) { project.repository.checksum }
|
||||
|
||||
let(:repository_double) { double(:repository) }
|
||||
let(:original_repository_double) { double(:repository) }
|
||||
let(:repository_checksum) { repository.checksum }
|
||||
|
||||
before do
|
||||
|
|
@ -14,10 +16,16 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
|
|||
allow(Gitlab::Git::Repository).to receive(:new)
|
||||
.with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path)
|
||||
.and_return(project_repository_double)
|
||||
allow(Gitlab::Git::Repository).to receive(:new)
|
||||
.with('default', project.repository.raw.relative_path, nil, nil)
|
||||
.and_return(original_project_repository_double)
|
||||
|
||||
allow(Gitlab::Git::Repository).to receive(:new)
|
||||
.with('test_second_storage', repository.raw.relative_path, repository.gl_repository, repository.full_path)
|
||||
.and_return(repository_double)
|
||||
allow(Gitlab::Git::Repository).to receive(:new)
|
||||
.with('default', repository.raw.relative_path, nil, nil)
|
||||
.and_return(original_repository_double)
|
||||
end
|
||||
|
||||
context 'when the move succeeds', :clean_gitlab_redis_shared_state do
|
||||
|
|
@ -35,8 +43,8 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
|
|||
allow(repository_double).to receive(:checksum)
|
||||
.and_return(repository_checksum)
|
||||
|
||||
expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything)
|
||||
.twice.and_call_original
|
||||
expect(original_project_repository_double).to receive(:remove)
|
||||
expect(original_repository_double).to receive(:remove)
|
||||
end
|
||||
|
||||
it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do
|
||||
|
|
@ -110,13 +118,36 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
|
|||
.with(repository.raw)
|
||||
.and_raise(Gitlab::Git::CommandError)
|
||||
|
||||
expect(GitlabShellWorker).not_to receive(:perform_async)
|
||||
|
||||
result = subject.execute
|
||||
|
||||
expect(result).to be_error
|
||||
expect(project).not_to be_repository_read_only
|
||||
expect(project.repository_storage).to eq('default')
|
||||
expect(repository_storage_move).to be_failed
|
||||
end
|
||||
end
|
||||
|
||||
context "when the cleanup of the #{repository_type} repository fails" do
|
||||
it 'sets the correct state' do
|
||||
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
|
||||
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
|
||||
allow(project_repository_double).to receive(:replicate)
|
||||
.with(project.repository.raw)
|
||||
allow(project_repository_double).to receive(:checksum)
|
||||
.and_return(project_repository_checksum)
|
||||
allow(original_project_repository_double).to receive(:remove)
|
||||
allow(repository_double).to receive(:replicate)
|
||||
.with(repository.raw)
|
||||
allow(repository_double).to receive(:checksum)
|
||||
.and_return(repository_checksum)
|
||||
|
||||
expect(original_repository_double).to receive(:remove)
|
||||
.and_raise(Gitlab::Git::CommandError)
|
||||
|
||||
result = subject.execute
|
||||
|
||||
expect(result).to be_error
|
||||
expect(repository_storage_move).to be_cleanup_failed
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -134,8 +165,6 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
|
|||
allow(repository_double).to receive(:checksum)
|
||||
.and_return('not matching checksum')
|
||||
|
||||
expect(GitlabShellWorker).not_to receive(:perform_async)
|
||||
|
||||
result = subject.execute
|
||||
|
||||
expect(result).to be_error
|
||||
|
|
|
|||
|
|
@ -848,10 +848,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.158.0.tgz#300d416184a2b0e05f15a96547f726e1825b08a1"
|
||||
integrity sha512-5OJl+7TsXN9PJhY6/uwi+mTwmDZa9n/6119rf77orQ/joFYUypaYhBmy/1TcKVPsy5Zs6KCxE1kmGsfoXc1TYA==
|
||||
|
||||
"@gitlab/ui@18.6.2":
|
||||
version "18.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-18.6.2.tgz#92d18dd36482ae412500820835e9d62f30f108d4"
|
||||
integrity sha512-3g22Q9RM1rmexipsZdroETJXd20+Fam1CHsC1h8vOWV4Fad5u3lgQp3KIQQlbmROIGTJ4PbiwE1Qldg+XAMsUw==
|
||||
"@gitlab/ui@18.7.0":
|
||||
version "18.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-18.7.0.tgz#aee0054d50e50aaf9e7c4ea4b9e36ca4b97102bf"
|
||||
integrity sha512-y1Gix1aCHvVO+zh6TCDmsCr97nLLHFnfEZRtg69EBnLBCLgwBcucC3mNeR4Q2EHTWjy/5U035UkyW6LDRX05mA==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue