Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-08-13 12:09:50 +00:00
parent 4e33606f01
commit 5605efec12
60 changed files with 526 additions and 293 deletions

View File

@ -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>

View File

@ -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', {});
},

View File

@ -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' }],
});
}

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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 `/`

View File

@ -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"

View File

@ -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"

View File

@ -4,8 +4,6 @@ export default {};
<template>
<div>
<transition name="slide">
<router-view ref="router-view" />
</transition>
<router-view ref="router-view" />
</div>
</template>

View File

@ -130,7 +130,7 @@ export default {
</script>
<template>
<div class="w-100 slide-enter-from-element">
<div>
<gl-alert
v-if="showDeleteAlert"
:variant="deleteAlertType"

View File

@ -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') }}

View File

@ -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';

View File

@ -243,10 +243,6 @@
content: '\f187';
}
.fa-sign-out::before {
content: '\f08b';
}
.fa-thumb-tack::before {
content: '\f08d';
}

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Preserve active tab on alert details page reload
merge_request: 39369
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Make available new UI for adding a panel to a metrics dashboard
merge_request: 39124
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Skip subsequent topology Prometheus queries if timeout occur
merge_request: 38293
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Remove transition animation from the Container Registry UI
merge_request: 39337
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove repositories from previous storage when storage move succeeds
merge_request: 38547
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add external link icon to list of repositories in importer
merge_request: 39442
author:
type: added

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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.
![New value stream](img/new_value_stream_v13_3.png "Creating a new value stream")
### 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.

View File

@ -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?

View File

@ -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 =

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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', () => {

View File

@ -42,9 +42,6 @@ describe('Actions menu', () => {
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(ActionsMenu, {
propsData: { ...dashboardActionsMenuProps, ...props },
provide: {
glFeatures: { metricsDashboardNewPanelPage: true },
},
store,
...options,
});

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -3,6 +3,7 @@ require 'spec_helper'
RSpec.describe API::ConanPackages do
include WorkhorseHelpers
include HttpBasicAuthHelpers
include PackagesManagerApiSpecHelpers
let(:package) { create(:conan_package) }

View File

@ -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

View File

@ -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) }

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"