Skip to content
Snippets Groups Projects
Commit aa0f0e99 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent d47f9d23
No related branches found
No related tags found
No related merge requests found
Showing
with 559 additions and 105 deletions
Loading
Loading
@@ -17,10 +17,13 @@ import createFlash from '~/flash';
import Icon from '~/vue_shared/components/icon.vue';
import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import DateTimePicker from './date_time_picker/date_time_picker.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
import GroupEmptyState from './group_empty_state.vue';
import DashboardsDropdown from './dashboards_dropdown.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getTimeDiff, getAddMetricTrackingOptions } from '../utils';
import { metricStates } from '../constants';
Loading
Loading
@@ -31,16 +34,18 @@ export default {
components: {
VueDraggable,
PanelType,
GraphGroup,
EmptyState,
GroupEmptyState,
Icon,
GlButton,
GlDropdown,
GlDropdownItem,
GlFormGroup,
GlModal,
DateTimePicker,
GraphGroup,
EmptyState,
GroupEmptyState,
DashboardsDropdown,
},
directives: {
GlModal: GlModalDirective,
Loading
Loading
@@ -83,6 +88,10 @@ export default {
type: String,
required: true,
},
defaultBranch: {
type: String,
required: true,
},
metricsEndpoint: {
type: String,
required: true,
Loading
Loading
@@ -140,6 +149,11 @@ export default {
required: false,
default: invalidUrl,
},
dashboardsEndpoint: {
type: String,
required: false,
default: invalidUrl,
},
currentDashboard: {
type: String,
required: false,
Loading
Loading
@@ -199,9 +213,6 @@ export default {
selectedDashboard() {
return this.allDashboards.find(d => d.path === this.currentDashboard) || this.firstDashboard;
},
selectedDashboardText() {
return this.selectedDashboard.display_name;
},
showRearrangePanelsBtn() {
return !this.showEmptyState && this.rearrangePanelsAvailable;
},
Loading
Loading
@@ -223,6 +234,7 @@ export default {
environmentsEndpoint: this.environmentsEndpoint,
deploymentsEndpoint: this.deploymentsEndpoint,
dashboardEndpoint: this.dashboardEndpoint,
dashboardsEndpoint: this.dashboardsEndpoint,
currentDashboard: this.currentDashboard,
projectPath: this.projectPath,
});
Loading
Loading
@@ -314,6 +326,13 @@ export default {
return !this.getMetricStates(groupKey).includes(metricStates.OK);
},
getAddMetricTrackingOptions,
selectDashboard(dashboard) {
const params = {
dashboard: dashboard.path,
};
redirectTo(mergeUrlParams(params, window.location.href));
},
},
addMetric: {
title: s__('Metrics|Add metric'),
Loading
Loading
@@ -333,21 +352,14 @@ export default {
label-for="monitor-dashboards-dropdown"
class="col-sm-12 col-md-6 col-lg-2"
>
<gl-dropdown
<dashboards-dropdown
id="monitor-dashboards-dropdown"
class="mb-0 d-flex js-dashboards-dropdown"
class="mb-0 d-flex"
toggle-class="dropdown-menu-toggle"
:text="selectedDashboardText"
>
<gl-dropdown-item
v-for="dashboard in allDashboards"
:key="dashboard.path"
:active="dashboard.path === currentDashboard"
active-class="is-active"
:href="`?dashboard=${dashboard.path}`"
>{{ dashboard.display_name || dashboard.path }}</gl-dropdown-item
>
</gl-dropdown>
:default-branch="defaultBranch"
:selected-dashboard="selectedDashboard"
@selectDashboard="selectDashboard($event)"
/>
</gl-form-group>
 
<gl-form-group
Loading
Loading
<script>
import { mapState, mapActions } from 'vuex';
import {
GlAlert,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlModal,
GlLoadingIcon,
GlModalDirective,
} from '@gitlab/ui';
import DuplicateDashboardForm from './duplicate_dashboard_form.vue';
const events = {
selectDashboard: 'selectDashboard',
};
export default {
components: {
GlAlert,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlModal,
GlLoadingIcon,
DuplicateDashboardForm,
},
directives: {
GlModal: GlModalDirective,
},
props: {
selectedDashboard: {
type: Object,
required: false,
default: () => ({}),
},
defaultBranch: {
type: String,
required: true,
},
},
data() {
return {
alert: null,
loading: false,
form: {},
};
},
computed: {
...mapState('monitoringDashboard', ['allDashboards']),
isSystemDashboard() {
return this.selectedDashboard.system_dashboard;
},
selectedDashboardText() {
return this.selectedDashboard.display_name;
},
},
methods: {
...mapActions('monitoringDashboard', ['duplicateSystemDashboard']),
selectDashboard(dashboard) {
this.$emit(events.selectDashboard, dashboard);
},
ok(bvModalEvt) {
// Prevent modal from hiding in case submit fails
bvModalEvt.preventDefault();
this.loading = true;
this.alert = null;
this.duplicateSystemDashboard(this.form)
.then(createdDashboard => {
this.loading = false;
this.alert = null;
// Trigger hide modal as submit is successful
this.$refs.duplicateDashboardModal.hide();
// Dashboards in the default branch become available immediately.
// Not so in other branches, so we refresh the current dashboard
const dashboard =
this.form.branch === this.defaultBranch ? createdDashboard : this.selectedDashboard;
this.$emit(events.selectDashboard, dashboard);
})
.catch(error => {
this.loading = false;
this.alert = error;
});
},
hide() {
this.alert = null;
},
formChange(form) {
this.form = form;
},
},
};
</script>
<template>
<gl-dropdown toggle-class="dropdown-menu-toggle" :text="selectedDashboardText">
<gl-dropdown-item
v-for="dashboard in allDashboards"
:key="dashboard.path"
:active="dashboard.path === selectedDashboard.path"
active-class="is-active"
@click="selectDashboard(dashboard)"
>
{{ dashboard.display_name || dashboard.path }}
</gl-dropdown-item>
<template v-if="isSystemDashboard">
<gl-dropdown-divider />
<gl-modal
ref="duplicateDashboardModal"
modal-id="duplicateDashboardModal"
:title="s__('Metrics|Duplicate dashboard')"
ok-variant="success"
@ok="ok"
@hide="hide"
>
<gl-alert v-if="alert" class="mb-3" variant="danger" @dismiss="alert = null">
{{ alert }}
</gl-alert>
<duplicate-dashboard-form
:dashboard="selectedDashboard"
:default-branch="defaultBranch"
@change="formChange"
/>
<template #modal-ok>
<gl-loading-icon v-if="loading" inline color="light" />
{{ loading ? s__('Metrics|Duplicating...') : s__('Metrics|Duplicate') }}
</template>
</gl-modal>
<gl-dropdown-item ref="duplicateDashboardItem" v-gl-modal="'duplicateDashboardModal'">
{{ s__('Metrics|Duplicate dashboard') }}
</gl-dropdown-item>
</template>
</gl-dropdown>
</template>
<script>
import { __, s__, sprintf } from '~/locale';
import { GlFormGroup, GlFormInput, GlFormRadioGroup, GlFormTextarea } from '@gitlab/ui';
const defaultFileName = dashboard => dashboard.path.split('/').reverse()[0];
export default {
components: {
GlFormGroup,
GlFormInput,
GlFormRadioGroup,
GlFormTextarea,
},
props: {
dashboard: {
type: Object,
required: true,
},
defaultBranch: {
type: String,
required: true,
},
},
radioVals: {
/* Use the default branch (e.g. master) */
DEFAULT: 'DEFAULT',
/* Create a new branch */
NEW: 'NEW',
},
data() {
return {
form: {
dashboard: this.dashboard.path,
fileName: defaultFileName(this.dashboard),
commitMessage: '',
},
branchName: '',
branchOption: this.$options.radioVals.NEW,
branchOptions: [
{
value: this.$options.radioVals.DEFAULT,
html: sprintf(
__('Commit to %{branchName} branch'),
{
branchName: `<strong>${this.defaultBranch}</strong>`,
},
false,
),
},
{ value: this.$options.radioVals.NEW, text: __('Create new branch') },
],
};
},
computed: {
defaultCommitMsg() {
return sprintf(s__('Metrics|Create custom dashboard %{fileName}'), {
fileName: this.form.fileName,
});
},
fileNameState() {
// valid if empty or *.yml
return !(this.form.fileName && !this.form.fileName.endsWith('.yml'));
},
fileNameFeedback() {
return !this.fileNameState ? s__('The file name should have a .yml extension') : '';
},
},
mounted() {
this.change();
},
methods: {
change() {
this.$emit('change', {
...this.form,
commitMessage: this.form.commitMessage || this.defaultCommitMsg,
branch:
this.branchOption === this.$options.radioVals.NEW ? this.branchName : this.defaultBranch,
});
},
focus(option) {
if (option === this.$options.radioVals.NEW) {
this.$nextTick(() => {
this.$refs.branchName.$el.focus();
});
}
},
},
};
</script>
<template>
<form @change="change">
<p class="text-muted">
{{
s__(`Metrics|You can save a copy of this dashboard to your repository
so it can be customized. Select a file name and branch to
save it.`)
}}
</p>
<gl-form-group
ref="fileNameFormGroup"
:label="__('File name')"
:state="fileNameState"
:invalid-feedback="fileNameFeedback"
label-size="sm"
label-for="fileName"
>
<gl-form-input id="fileName" ref="fileName" v-model="form.fileName" :required="true" />
</gl-form-group>
<gl-form-group :label="__('Branch')" label-size="sm" label-for="branch">
<gl-form-radio-group
ref="branchOption"
v-model="branchOption"
:checked="$options.radioVals.NEW"
:stacked="true"
:options="branchOptions"
@change="focus"
/>
<gl-form-input
v-show="branchOption === $options.radioVals.NEW"
id="branchName"
ref="branchName"
v-model="branchName"
/>
</gl-form-group>
<gl-form-group
:label="__('Commit message (optional)')"
label-size="sm"
label-for="commitMessage"
>
<gl-form-textarea
id="commitMessage"
ref="commitMessage"
v-model="form.commitMessage"
:placeholder="defaultCommitMsg"
/>
</gl-form-group>
</form>
</template>
Loading
Loading
@@ -214,5 +214,29 @@ export const setPanelGroupMetrics = ({ commit }, data) => {
commit(types.SET_PANEL_GROUP_METRICS, data);
};
 
export const duplicateSystemDashboard = ({ state }, payload) => {
const params = {
dashboard: payload.dashboard,
file_name: payload.fileName,
branch: payload.branch,
commit_message: payload.commitMessage,
};
return axios
.post(state.dashboardsEndpoint, params)
.then(response => response.data)
.then(data => data.dashboard)
.catch(error => {
const { response } = error;
if (response && response.data && response.data.error) {
throw sprintf(s__('Metrics|There was an error creating the dashboard. %{error}'), {
error: response.data.error,
});
} else {
throw s__('Metrics|There was an error creating the dashboard.');
}
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
Loading
Loading
@@ -175,6 +175,7 @@ export default {
state.environmentsEndpoint = endpoints.environmentsEndpoint;
state.deploymentsEndpoint = endpoints.deploymentsEndpoint;
state.dashboardEndpoint = endpoints.dashboardEndpoint;
state.dashboardsEndpoint = endpoints.dashboardsEndpoint;
state.currentDashboard = endpoints.currentDashboard;
state.projectPath = endpoints.projectPath;
},
Loading
Loading
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import DeploymentInfo from './deployment_info.vue';
import DeploymentViewButton from './deployment_view_button.vue';
import DeploymentStopButton from './deployment_stop_button.vue';
Loading
Loading
@@ -14,9 +14,6 @@ export default {
DeploymentStopButton,
DeploymentViewButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
deployment: {
type: Object,
Loading
Loading
@@ -43,6 +40,14 @@ export default {
},
},
computed: {
appButtonText() {
return {
text: this.isCurrent ? s__('Review App|View app') : s__('Review App|View latest app'),
tooltip: this.isCurrent
? ''
: __('View the latest successful deployment to this environment'),
};
},
canBeManuallyDeployed() {
return this.computedDeploymentStatus === MANUAL_DEPLOY;
},
Loading
Loading
@@ -55,9 +60,6 @@ export default {
hasExternalUrls() {
return Boolean(this.deployment.external_url && this.deployment.external_url_formatted);
},
hasPreviousDeployment() {
return Boolean(!this.isCurrent && this.deployment.deployed_at);
},
isCurrent() {
return this.computedDeploymentStatus === SUCCESS;
},
Loading
Loading
@@ -89,7 +91,7 @@ export default {
<!-- show appropriate version of review app button -->
<deployment-view-button
v-if="hasExternalUrls"
:is-current="isCurrent"
:app-button-text="appButtonText"
:deployment="deployment"
:show-visual-review-app="showVisualReviewApp"
:visual-review-app-metadata="visualReviewAppMeta"
Loading
Loading
Loading
Loading
@@ -11,12 +11,12 @@ export default {
import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'),
},
props: {
deployment: {
appButtonText: {
type: Object,
required: true,
},
isCurrent: {
type: Boolean,
deployment: {
type: Object,
required: true,
},
showVisualReviewApp: {
Loading
Loading
@@ -60,7 +60,7 @@ export default {
>
<template slot="mainAction" slot-scope="slotProps">
<review-app-link
:is-current="isCurrent"
:display="appButtonText"
:link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/>
Loading
Loading
@@ -85,7 +85,7 @@ export default {
</filtered-search-dropdown>
<template v-else>
<review-app-link
:is-current="isCurrent"
:display="appButtonText"
:link="deploymentExternalUrl"
css-class="js-deploy-url deploy-link btn btn-default btn-sm inline"
/>
Loading
Loading
<script>
import { __ } from '~/locale';
import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
 
export default {
components: {
Icon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
cssClass: {
type: String,
required: true,
},
isCurrent: {
type: Boolean,
display: {
type: Object,
required: true,
},
link: {
Loading
Loading
@@ -20,15 +23,12 @@ export default {
required: true,
},
},
computed: {
linkText() {
return this.isCurrent ? __('View app') : __('View previous app');
},
},
};
</script>
<template>
<a
v-gl-tooltip
:title="display.tooltip"
:href="link"
target="_blank"
rel="noopener noreferrer nofollow"
Loading
Loading
@@ -36,6 +36,6 @@ export default {
data-track-event="open_review_app"
data-track-label="review_app"
>
{{ linkText }} <icon class="fgray" name="external-link" />
{{ display.text }} <icon class="fgray" name="external-link" />
</a>
</template>
Loading
Loading
@@ -7,90 +7,53 @@ module Projects
 
before_action :check_repository_available!
before_action :validate_required_params!
before_action :validate_dashboard_template!
before_action :authorize_push!
 
USER_DASHBOARDS_DIR = ::Metrics::Dashboard::ProjectDashboardService::DASHBOARD_ROOT
DASHBOARD_TEMPLATES = {
::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH => ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH
}.freeze
rescue_from ActionController::ParameterMissing do |exception|
respond_error(http_status: :bad_request, message: _('Request parameter %{param} is missing.') % { param: exception.param })
end
 
def create
result = ::Files::CreateService.new(project, current_user, dashboard_attrs).execute
result = ::Metrics::Dashboard::CloneDashboardService.new(project, current_user, dashboard_params).execute
 
if result[:status] == :success
respond_success
respond_success(result)
else
respond_error(result[:message])
respond_error(result)
end
end
 
private
 
def respond_success
def respond_success(result)
set_web_ide_link_notice(result.dig(:dashboard, :path))
respond_to do |format|
format.html { redirect_to ide_edit_path(project, redirect_safe_branch_name, new_dashboard_path) }
format.json { render json: { redirect_to: ide_edit_path(project, redirect_safe_branch_name, new_dashboard_path) }, status: :created }
format.json { render status: result.delete(:http_status), json: result }
end
end
 
def respond_error(message)
flash[:alert] = message
def respond_error(result)
respond_to do |format|
format.html { redirect_back_or_default(default: namespace_project_environments_path) }
format.json { render json: { error: message }, status: :bad_request }
format.json { render json: { error: result[:message] }, status: result[:http_status] }
end
end
 
def authorize_push!
access_denied!(%q(You can't commit to this project)) unless user_access(project).can_push_to_branch?(params[:branch])
def set_web_ide_link_notice(new_dashboard_path)
web_ide_link_start = "<a href=\"#{ide_edit_path(project, redirect_safe_branch_name, new_dashboard_path)}\">"
message = _("Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}.") % { web_ide_link_start: web_ide_link_start, web_ide_link_end: "</a>" }
flash[:notice] = message.html_safe
end
 
def validate_required_params!
params.require(%i(branch file_name dashboard))
end
def validate_dashboard_template!
access_denied! unless dashboard_template
end
def dashboard_attrs
{
commit_message: commit_message,
file_path: new_dashboard_path,
file_content: new_dashboard_content,
encoding: 'text',
branch_name: params[:branch],
start_branch: repository.branch_exists?(params[:branch]) ? params[:branch] : project.default_branch
}
end
def commit_message
params[:commit_message] || "Create custom dashboard #{params[:file_name]}"
end
def new_dashboard_path
File.join(USER_DASHBOARDS_DIR, params[:file_name])
end
def new_dashboard_content
File.read(Rails.root.join(dashboard_template))
end
def dashboard_template
dashboard_templates[params[:dashboard]]
end
def dashboard_templates
DASHBOARD_TEMPLATES
params.require(%i(branch file_name dashboard commit_message))
end
 
def redirect_safe_branch_name
repository.find_branch(params[:branch]).name
end
def dashboard_params
params.permit(%i(branch file_name dashboard commit_message)).to_h
end
end
end
end
Projects::PerformanceMonitoring::DashboardsController.prepend_if_ee('EE::Projects::PerformanceMonitoring::DashboardsController')
# frozen_string_literal: true
module Resolvers
class EnvironmentsResolver < BaseResolver
argument :name, GraphQL::STRING_TYPE,
required: false,
description: 'Name of the environment'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query'
type Types::EnvironmentType, null: true
alias_method :project, :object
def resolve(**args)
return unless project.present?
EnvironmentsFinder.new(project, context[:current_user], args).find
end
end
end
# frozen_string_literal: true
module Types
class EnvironmentType < BaseObject
graphql_name 'Environment'
description 'Describes where code is deployed for a project'
authorize :read_environment
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Human-readable name of the environment'
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the environment'
end
end
Loading
Loading
@@ -138,6 +138,12 @@ module Types
description: 'Issues of the project',
resolver: Resolvers::IssuesResolver
 
field :environments,
Types::EnvironmentType.connection_type,
null: true,
description: 'Environments of the project',
resolver: Resolvers::EnvironmentsResolver
field :issue,
Types::IssueType,
null: true,
Loading
Loading
Loading
Loading
@@ -29,8 +29,10 @@ module EnvironmentsHelper
"empty-no-data-small-svg-path" => image_path('illustrations/chart-empty-state-small.svg'),
"empty-unable-to-connect-svg-path" => image_path('illustrations/monitoring/unable_to_connect.svg'),
"metrics-endpoint" => additional_metrics_project_environment_path(project, environment, format: :json),
"dashboards-endpoint" => project_performance_monitoring_dashboards_path(project, format: :json),
"dashboard-endpoint" => metrics_dashboard_project_environment_path(project, environment, format: :json),
"deployments-endpoint" => project_environment_deployments_path(project, environment, format: :json),
"default-branch" => project.default_branch,
"environments-endpoint": project_environments_path(project, format: :json),
"project-path" => project_path(project),
"tags-path" => project_tags_path(project),
Loading
Loading
Loading
Loading
@@ -197,6 +197,10 @@ module Ci
 
AutoMergeProcessWorker.perform_async(merge_request.id)
end
if pipeline.auto_devops_source?
self.class.auto_devops_pipelines_completed_total.increment(status: pipeline.status)
end
end
end
 
Loading
Loading
@@ -330,6 +334,10 @@ module Ci
::Ci::Pipeline::AVAILABLE_STATUSES - %w[created waiting_for_resource preparing pending]
end
 
def self.auto_devops_pipelines_completed_total
@auto_devops_pipelines_completed_total ||= Gitlab::Metrics.counter(:auto_devops_pipelines_completed_total, 'Number of completed auto devops pipelines')
end
def stages_count
statuses.select(:stage).distinct.count
end
Loading
Loading
Loading
Loading
@@ -307,6 +307,8 @@ class User < ApplicationRecord
scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :external, -> { where(external: true) }
scope :active, -> { with_state(:active).non_internal }
scope :active_without_ghosts, -> { with_state(:active).without_ghosts }
scope :without_ghosts, -> { where('ghost IS NOT TRUE') }
scope :deactivated, -> { with_state(:deactivated).non_internal }
scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) }
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
Loading
Loading
@@ -470,7 +472,7 @@ class User < ApplicationRecord
when 'deactivated'
deactivated
else
active
active_without_ghosts
end
end
 
Loading
Loading
@@ -614,7 +616,7 @@ class User < ApplicationRecord
end
 
def self.non_internal
where('ghost IS NOT TRUE')
without_ghosts
end
 
#
Loading
Loading
# frozen_string_literal: true
# Copies system dashboard definition in .yml file into designated
# .yml file inside `.gitlab/dashboards`
module Metrics
module Dashboard
class CloneDashboardService < ::BaseService
ALLOWED_FILE_TYPE = '.yml'
USER_DASHBOARDS_DIR = ::Metrics::Dashboard::ProjectDashboardService::DASHBOARD_ROOT
def self.allowed_dashboard_templates
@allowed_dashboard_templates ||= Set[::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH].freeze
end
def execute
catch(:error) do
throw(:error, error(_(%q(You can't commit to this project)), :forbidden)) unless push_authorized?
result = ::Files::CreateService.new(project, current_user, dashboard_attrs).execute
throw(:error, wrap_error(result)) unless result[:status] == :success
repository.refresh_method_caches([:metrics_dashboard])
success(result.merge(http_status: :created, dashboard: dashboard_details))
end
end
private
def dashboard_attrs
{
commit_message: params[:commit_message],
file_path: new_dashboard_path,
file_content: new_dashboard_content,
encoding: 'text',
branch_name: branch,
start_branch: repository.branch_exists?(branch) ? branch : project.default_branch
}
end
def dashboard_details
{
path: new_dashboard_path,
display_name: ::Metrics::Dashboard::ProjectDashboardService.name_for_path(new_dashboard_path),
default: false,
system_dashboard: false
}
end
def push_authorized?
Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch)
end
def dashboard_template
@dashboard_template ||= begin
throw(:error, error(_('Not found.'), :not_found)) unless self.class.allowed_dashboard_templates.include?(params[:dashboard])
params[:dashboard]
end
end
def branch
@branch ||= begin
throw(:error, error(_('There was an error creating the dashboard, branch name is invalid.'), :bad_request)) unless valid_branch_name?
throw(:error, error(_('There was an error creating the dashboard, branch named: %{branch} already exists.') % { branch: params[:branch] }, :bad_request)) unless new_or_default_branch? # temporary validation for first UI iteration
params[:branch]
end
end
def new_or_default_branch?
!repository.branch_exists?(params[:branch]) || project.default_branch == params[:branch]
end
def valid_branch_name?
Gitlab::GitRefValidator.validate(params[:branch])
end
def new_dashboard_path
@new_dashboard_path ||= File.join(USER_DASHBOARDS_DIR, file_name)
end
def file_name
@file_name ||= begin
throw(:error, error(_('The file name should have a .yml extension'), :bad_request)) unless target_file_type_valid?
File.basename(params[:file_name])
end
end
def target_file_type_valid?
File.extname(params[:file_name]) == ALLOWED_FILE_TYPE
end
def new_dashboard_content
File.read(Rails.root.join(dashboard_template))
end
def repository
@repository ||= project.repository
end
def wrap_error(result)
if result[:message] == 'A file with this name already exists'
error(_("A file with '%{file_name}' already exists in %{branch} branch") % { file_name: file_name, branch: branch }, :bad_request)
else
result
end
end
end
end
end
Metrics::Dashboard::CloneDashboardService.prepend_if_ee('EE::Metrics::Dashboard::CloneDashboardService')
Loading
Loading
@@ -9,7 +9,7 @@
= nav_link(html_options: { class: active_when(params[:filter].nil?) }) do
= link_to admin_users_path do
= s_('AdminUsers|Active')
%small.badge.badge-pill= limited_counter_with_delimiter(User.active)
%small.badge.badge-pill= limited_counter_with_delimiter(User.active_without_ghosts)
= nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do
= link_to admin_users_path(filter: "admins") do
= s_('AdminUsers|Admins')
Loading
Loading
Loading
Loading
@@ -44,8 +44,10 @@
= expanded ? _('Collapse') : _('Expand')
%p
- auto_devops_url = help_page_path('topics/autodevops/index')
- quickstart_url = help_page_path('topics/autodevops/quick_start_guide')
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
= s_('GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
- quickstart_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: quickstart_url }
= s_('AutoDevOps|Auto DevOps can automatically build, test, and deploy applications based on predefined continuous integration and delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end} or use our %{quickstart_start}quick start guide%{quickstart_end} to get started right away.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe, quickstart_start: quickstart_start, quickstart_end: '</a>'.html_safe }
 
.settings-content
= render 'groups/settings/ci_cd/auto_devops_form', group: @group
Loading
Loading
@@ -44,7 +44,7 @@
 
- if group_sidebar_link?(:contribution_analytics)
= nav_link(path: 'analytics#show') do
= link_to group_analytics_path(@group), title: _('Contribution Analytics'), data: { placement: 'right', qa_selector: 'contribution_analytics_link' } do
= link_to group_contribution_analytics_path(@group), title: _('Contribution Analytics'), data: { placement: 'right', qa_selector: 'contribution_analytics_link' } do
%span
= _('Contribution Analytics')
 
Loading
Loading
Loading
Loading
@@ -23,8 +23,11 @@
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= s_('CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.')
= link_to s_('CICD|Learn more about Auto DevOps'), help_page_path('topics/autodevops/index.md')
- auto_devops_url = help_page_path('topics/autodevops/index')
- quickstart_url = help_page_path('topics/autodevops/quick_start_guide')
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
- quickstart_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: quickstart_url }
= s_('AutoDevOps|Auto DevOps can automatically build, test, and deploy applications based on predefined continuous integration and delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end} or use our %{quickstart_start}quick start guide%{quickstart_end} to get started right away.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe, quickstart_start: quickstart_start, quickstart_end: '</a>'.html_safe }
.settings-content
= render 'autodevops_form', auto_devops_enabled: @project.auto_devops_enabled?
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment