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

Add latest changes from gitlab-org/gitlab@master

parent 1a9d9cc1
No related branches found
No related tags found
No related merge requests found
Showing
with 315 additions and 126 deletions
Loading
Loading
@@ -50,7 +50,6 @@ rules:
# all offenses of no-jquery/no-animate-toggle are false positives ( $toast.show() )
no-jquery/no-animate-toggle: off
no-jquery/no-event-shorthand: off
no-jquery/no-fade: off
no-jquery/no-serialize: error
no-jquery/no-sizzle: off
promise/always-return: off
Loading
Loading
Loading
Loading
@@ -458,7 +458,7 @@ gem 'grpc', '~> 1.24.0'
 
gem 'google-protobuf', '~> 3.8.0'
 
gem 'toml-rb', '~> 1.0.0', require: false
gem 'toml-rb', '~> 1.0.0'
 
# Feature toggles
gem 'flipper', '~> 0.17.1'
Loading
Loading
Loading
Loading
@@ -40,7 +40,10 @@ export default class ImageFile {
.removeClass('active')
.filter(`.${viewMode}`)
.addClass('active');
// eslint-disable-next-line no-jquery/no-fade
return $(`.view:visible:not(.${viewMode})`, this.file).fadeOut(200, () => {
// eslint-disable-next-line no-jquery/no-fade
$(`.view.${viewMode}`, this.file).fadeIn(200);
return this.initView(viewMode);
});
Loading
Loading
Loading
Loading
@@ -116,11 +116,13 @@ class DueDateSelect {
}
 
updateIssueBoardIssue() {
// eslint-disable-next-line no-jquery/no-fade
this.$loading.fadeIn();
this.$dropdown.trigger('loading.gl.dropdown');
this.$selectbox.hide();
this.$value.css('display', '');
const fadeOutLoader = () => {
// eslint-disable-next-line no-jquery/no-fade
this.$loading.fadeOut();
};
 
Loading
Loading
@@ -135,6 +137,7 @@ class DueDateSelect {
const hasDueDate = this.displayedDate !== __('None');
const displayedDateStyle = hasDueDate ? 'bold' : 'no-value';
 
// eslint-disable-next-line no-jquery/no-fade
this.$loading.removeClass('hidden').fadeIn();
 
if (isDropdown) {
Loading
Loading
@@ -158,6 +161,7 @@ class DueDateSelect {
}
this.$sidebarCollapsedValue.attr('data-original-title', tooltipText);
 
// eslint-disable-next-line no-jquery/no-fade
return this.$loading.fadeOut();
});
}
Loading
Loading
Loading
Loading
@@ -25,10 +25,33 @@ export default {
PREV_PAGE: 1,
NEXT_PAGE: 2,
fields: [
{ key: 'error', label: __('Open errors'), thClass: 'w-70p' },
{ key: 'events', label: __('Events') },
{ key: 'users', label: __('Users') },
{ key: 'lastSeen', label: __('Last seen'), thClass: 'w-15p' },
{
key: 'error',
label: __('Error'),
thClass: 'w-70p',
tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
},
{
key: 'events',
label: __('Events'),
tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
},
{
key: 'users',
label: __('Users'),
tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
},
{
key: 'lastSeen',
label: __('Last seen'),
thClass: 'w-15p',
tdClass: 'table-col d-flex align-items-center d-sm-table-cell',
},
{
key: 'details',
tdClass: 'table-col d-sm-none d-flex align-items-center',
thClass: 'invisible w-0',
},
],
sortFields: {
last_seen: __('Last Seen'),
Loading
Loading
@@ -149,61 +172,63 @@ export default {
<div class="error-list">
<div v-if="errorTrackingEnabled">
<div
class="d-flex flex-row justify-content-around align-items-center bg-secondary border mt-2"
class="row flex-column flex-sm-row align-items-sm-center row-top m-0 mt-sm-2 mx-sm-1 p-0 p-sm-3"
>
<div class="filtered-search-box flex-grow-1 my-3 ml-3 mr-2">
<gl-dropdown
:text="__('Recent searches')"
class="filtered-search-history-dropdown-wrapper d-none d-md-block"
toggle-class="filtered-search-history-dropdown-toggle-button"
:disabled="loading"
>
<div v-if="!$options.hasLocalStorage" class="px-3">
{{ __('This feature requires local storage to be enabled') }}
</div>
<template v-else-if="recentSearches.length > 0">
<gl-dropdown-item
v-for="searchQuery in recentSearches"
:key="searchQuery"
@click="setSearchText(searchQuery)"
>{{ searchQuery }}</gl-dropdown-item
>
<gl-dropdown-divider />
<gl-dropdown-item ref="clearRecentSearches" @click="clearRecentSearches">{{
__('Clear recent searches')
}}</gl-dropdown-item>
</template>
<div v-else class="px-3">{{ __("You don't have any recent searches") }}</div>
</gl-dropdown>
<div class="filtered-search-input-container flex-fill">
<gl-form-input
v-model="errorSearchQuery"
class="pl-2 filtered-search"
<div class="search-box flex-fill mr-sm-2 my-3 m-sm-0 p-3 p-sm-0">
<div class="filtered-search-box mb-0">
<gl-dropdown
:text="__('Recent searches')"
class="filtered-search-history-dropdown-wrapper"
toggle-class="filtered-search-history-dropdown-toggle-button"
:disabled="loading"
:placeholder="__('Search or filter results…')"
autofocus
@keyup.enter.native="searchByQuery(errorSearchQuery)"
/>
</div>
<div class="gl-search-box-by-type-right-icons">
<gl-button
v-if="errorSearchQuery.length > 0"
v-gl-tooltip.hover
:title="__('Clear')"
class="clear-search text-secondary"
name="clear"
@click="errorSearchQuery = ''"
>
<gl-icon name="close" :size="12" />
</gl-button>
<div v-if="!$options.hasLocalStorage" class="px-3">
{{ __('This feature requires local storage to be enabled') }}
</div>
<template v-else-if="recentSearches.length > 0">
<gl-dropdown-item
v-for="searchQuery in recentSearches"
:key="searchQuery"
@click="setSearchText(searchQuery)"
>{{ searchQuery }}
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-item ref="clearRecentSearches" @click="clearRecentSearches"
>{{ __('Clear recent searches') }}
</gl-dropdown-item>
</template>
<div v-else class="px-3">{{ __("You don't have any recent searches") }}</div>
</gl-dropdown>
<div class="filtered-search-input-container flex-fill">
<gl-form-input
v-model="errorSearchQuery"
class="pl-2 filtered-search"
:disabled="loading"
:placeholder="__('Search or filter results…')"
autofocus
@keyup.enter.native="searchByQuery(errorSearchQuery)"
/>
</div>
<div class="gl-search-box-by-type-right-icons">
<gl-button
v-if="errorSearchQuery.length > 0"
v-gl-tooltip.hover
:title="__('Clear')"
class="clear-search text-secondary"
name="clear"
@click="errorSearchQuery = ''"
>
<gl-icon name="close" :size="12" />
</gl-button>
</div>
</div>
</div>
 
<gl-dropdown
class="sort-control"
:text="$options.sortFields[sortField]"
left
:disabled="loading"
class="mr-3"
menu-class="sort-dropdown"
>
<gl-dropdown-item
Loading
Loading
@@ -227,62 +252,77 @@ export default {
<gl-loading-icon size="md" />
</div>
 
<gl-table
v-else
class="mt-3"
:items="errors"
:fields="$options.fields"
:show-empty="true"
fixed
stacked="sm"
>
<template slot="HEAD_events" slot-scope="data">
<div class="text-md-right">{{ data.label }}</div>
</template>
<template slot="HEAD_users" slot-scope="data">
<div class="text-md-right">{{ data.label }}</div>
</template>
<template slot="error" slot-scope="errors">
<div class="d-flex flex-column">
<gl-link class="d-flex text-dark" :href="getDetailsLink(errors.item.id)">
<strong class="text-truncate">{{ errors.item.title.trim() }}</strong>
</gl-link>
<span class="text-secondary text-truncate">
{{ errors.item.culprit }}
</span>
</div>
</template>
<template slot="events" slot-scope="errors">
<div class="text-md-right">{{ errors.item.count }}</div>
</template>
<template v-else>
<h4 class="d-block d-sm-none my-3">{{ __('Open errors') }}</h4>
 
<template slot="users" slot-scope="errors">
<div class="text-md-right">{{ errors.item.userCount }}</div>
</template>
<gl-table
class="mt-3"
:items="errors"
:fields="$options.fields"
:show-empty="true"
fixed
stacked="sm"
tbody-tr-class="table-row mb-4"
>
<template v-slot:head(error)>
<div class="d-none d-sm-block">{{ __('Open errors') }}</div>
</template>
<template v-slot:head(events)="data">
<div class="text-sm-right">{{ data.label }}</div>
</template>
<template v-slot:head(users)="data">
<div class="text-sm-right">{{ data.label }}</div>
</template>
 
<template slot="lastSeen" slot-scope="errors">
<div class="d-flex align-items-center">
<time-ago :time="errors.item.lastSeen" class="text-secondary" />
</div>
</template>
<template slot="empty">
<div ref="empty">
<template v-slot:error="errors">
<div class="d-flex flex-column">
<gl-link class="d-flex mw-100 text-dark" :href="getDetailsLink(errors.item.id)">
<strong class="text-truncate">{{ errors.item.title.trim() }}</strong>
</gl-link>
<span class="text-secondary text-truncate mw-100">
{{ errors.item.culprit }}
</span>
</div>
</template>
<template v-slot:events="errors">
<div class="text-right">{{ errors.item.count }}</div>
</template>
<template v-slot:users="errors">
<div class="text-right">{{ errors.item.userCount }}</div>
</template>
<template v-slot:lastSeen="errors">
<div class="text-md-left text-right">
<time-ago :time="errors.item.lastSeen" class="text-secondary" />
</div>
</template>
<template v-slot:details="errors">
<gl-button
:href="getDetailsLink(errors.item.id)"
variant="outline-info"
class="d-block"
>
{{ __('More details') }}
</gl-button>
</template>
<template v-slot:empty>
{{ __('No errors to display.') }}
<gl-link class="js-try-again" @click="restartPolling">
{{ __('Check again') }}
</gl-link>
</div>
</template>
</gl-table>
<gl-pagination
v-show="!loading"
v-if="paginationRequired"
:prev-page="$options.PREV_PAGE"
:next-page="$options.NEXT_PAGE"
:value="pageValue"
align="center"
@input="goToPage"
/>
</template>
</gl-table>
<gl-pagination
v-show="!loading"
v-if="paginationRequired"
:prev-page="$options.PREV_PAGE"
:next-page="$options.NEXT_PAGE"
:value="pageValue"
align="center"
@input="goToPage"
/>
</template>
</div>
<div v-else-if="userCanEnableErrorTracking">
<gl-empty-state
Loading
Loading
Loading
Loading
@@ -64,6 +64,7 @@ export default class FilterableList {
return false;
}
 
// eslint-disable-next-line no-jquery/no-fade
$(this.listHolderElement).fadeTo(250, 0.5);
 
this.isBusy = true;
Loading
Loading
@@ -98,6 +99,7 @@ export default class FilterableList {
 
onFilterComplete() {
this.isBusy = false;
// eslint-disable-next-line no-jquery/no-fade
$(this.listHolderElement).fadeTo(250, 1);
}
}
Loading
Loading
@@ -45,6 +45,7 @@ export default class LabelsSelect {
const $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
const $value = $block.find('.value');
const $dropdownMenu = $dropdown.parent().find('.dropdown-menu');
// eslint-disable-next-line no-jquery/no-fade
const $loading = $block.find('.block-loading').fadeOut();
const fieldName = $dropdown.data('fieldName');
let initialSelected = $selectbox
Loading
Loading
@@ -84,6 +85,7 @@ export default class LabelsSelect {
if (!selected.length) {
data[abilityName].label_ids = [''];
}
// eslint-disable-next-line no-jquery/no-fade
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
axios
Loading
Loading
@@ -91,6 +93,7 @@ export default class LabelsSelect {
.then(({ data }) => {
let labelTooltipTitle;
let template;
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide();
Loading
Loading
@@ -361,6 +364,7 @@ export default class LabelsSelect {
const label = clickEvent.selectedObj;
 
const fadeOutLoader = () => {
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
};
 
Loading
Loading
@@ -422,6 +426,7 @@ export default class LabelsSelect {
boardsStore.detail.issue.labels = labels;
}
 
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeIn();
const oldLabels = boardsStore.detail.issue.labels;
 
Loading
Loading
Loading
Loading
@@ -113,6 +113,7 @@ function deferredInitialisation() {
});
 
$('.js-remove-tr').on('ajax:success', function removeTRAjaxSuccessCallback() {
// eslint-disable-next-line no-jquery/no-fade
$(this)
.closest('tr')
.fadeOut();
Loading
Loading
Loading
Loading
@@ -52,6 +52,7 @@ export default class MilestoneSelect {
const $block = $selectBox.closest('.block');
const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
const $value = $block.find('.value');
// eslint-disable-next-line no-jquery/no-fade
const $loading = $block.find('.block-loading').fadeOut();
selectedMilestoneDefault = showAny ? '' : null;
selectedMilestoneDefault =
Loading
Loading
@@ -202,15 +203,18 @@ export default class MilestoneSelect {
}
 
$dropdown.trigger('loading.gl.dropdown');
// eslint-disable-next-line no-jquery/no-fade
$loading.removeClass('hidden').fadeIn();
 
boardsStore.detail.issue
.update($dropdown.attr('data-issue-update'))
.then(() => {
$dropdown.trigger('loaded.gl.dropdown');
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
})
.catch(() => {
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
});
} else {
Loading
Loading
@@ -218,12 +222,14 @@ export default class MilestoneSelect {
data = {};
data[abilityName] = {};
data[abilityName].milestone_id = selected != null ? selected : null;
// eslint-disable-next-line no-jquery/no-fade
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
return axios
.put(issueUpdateURL, data)
.then(({ data }) => {
$dropdown.trigger('loaded.gl.dropdown');
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
$selectBox.hide();
$value.css('display', '');
Loading
Loading
@@ -247,6 +253,7 @@ export default class MilestoneSelect {
}
})
.catch(() => {
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
});
}
Loading
Loading
Loading
Loading
@@ -336,6 +336,7 @@ export default {
 
<markdown-field
ref="markdownField"
:is-submitting="isSubmitting"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
Loading
Loading
Loading
Loading
@@ -53,6 +53,7 @@ function UsersSelect(currentUser, els, options = {}) {
const abilityName = $dropdown.data('abilityName');
let $value = $block.find('.value');
const $collapsedSidebar = $block.find('.sidebar-collapsed-user');
// eslint-disable-next-line no-jquery/no-fade
const $loading = $block.find('.block-loading').fadeOut();
const selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
let selectedId = $dropdown.data('selected');
Loading
Loading
@@ -188,6 +189,7 @@ function UsersSelect(currentUser, els, options = {}) {
const data = {};
data[abilityName] = {};
data[abilityName].assignee_id = selected != null ? selected : null;
// eslint-disable-next-line no-jquery/no-fade
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
 
Loading
Loading
@@ -195,6 +197,7 @@ function UsersSelect(currentUser, els, options = {}) {
let user = {};
let tooltipTitle = user.name;
$dropdown.trigger('loaded.gl.dropdown');
// eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
if (data.assignee) {
user = {
Loading
Loading
Loading
Loading
@@ -20,6 +20,11 @@ export default {
Suggestions,
},
props: {
isSubmitting: {
type: Boolean,
required: false,
default: false,
},
markdownPreviewPath: {
type: String,
required: false,
Loading
Loading
@@ -133,6 +138,20 @@ export default {
);
},
},
watch: {
isSubmitting(isSubmitting) {
if (!isSubmitting || !this.$refs['markdown-preview'].querySelectorAll) {
return;
}
const mediaInPreview = this.$refs['markdown-preview'].querySelectorAll('video, audio');
if (mediaInPreview) {
mediaInPreview.forEach(media => {
media.pause();
});
}
},
},
mounted() {
/*
GLForm class handles all the toolbar buttons
Loading
Loading
@@ -177,7 +196,6 @@ export default {
this.renderMarkdown();
}
},
showWriteTab() {
this.markdownPreview = '';
this.previewMarkdown = false;
Loading
Loading
$gray-border: 1px solid $border-color;
.error-list {
.sort-control {
.btn {
padding-right: 2rem;
}
.gl-dropdown-caret {
position: absolute;
right: 0.5rem;
top: 0.5rem;
}
}
@include media-breakpoint-up(sm) {
.row-top {
border: $gray-border;
background-color: $gray-50;
}
}
@include media-breakpoint-down(xs) {
.table-row {
border: $gray-border;
border-radius: 4px;
}
.search-box {
border-top: $gray-border;
border-bottom: $gray-border;
background-color: $gray-50;
}
.table-col {
min-height: 68px;
&::before {
text-align: left !important;
}
&:first-child {
div {
padding: 0 !important;
align-items: flex-end;
}
}
&:last-child {
height: 64px;
background-color: $gray-normal;
&::before {
content: none !important;
}
div {
width: 100% !important;
padding: 0 !important;
a {
color: $blue-500;
border-color: $blue-500;
}
}
}
}
}
}
Loading
Loading
@@ -51,6 +51,7 @@ class Blob < SimpleDelegator
BlobViewer::Contributing,
BlobViewer::Changelog,
 
BlobViewer::CargoToml,
BlobViewer::Cartfile,
BlobViewer::ComposerJson,
BlobViewer::Gemfile,
Loading
Loading
# frozen_string_literal: true
module BlobViewer
class CargoToml < DependencyManager
include Static
self.file_types = %i(cargo_toml)
def manager_name
'Cargo'
end
def manager_url
'https://crates.io/'
end
end
end
Loading
Loading
@@ -717,8 +717,8 @@ module Ci
end
end
 
def has_expiring_artifacts?
artifacts_expire_at.present? && artifacts_expire_at > Time.now
def has_expiring_archive_artifacts?
has_expiring_artifacts? && artifacts_file&.exists?
end
 
def keep_artifacts!
Loading
Loading
@@ -933,6 +933,10 @@ module Ci
value.with_indifferent_access
end
end
def has_expiring_artifacts?
artifacts_expire_at.present? && artifacts_expire_at > Time.now
end
end
end
 
Loading
Loading
Loading
Loading
@@ -23,6 +23,12 @@ class DiffNote < Note
 
before_validation :set_line_code, if: :on_text?, unless: :importing?
after_save :keep_around_commits, unless: :importing?
NoteDiffFileCreationError = Class.new(StandardError)
DIFF_LINE_NOT_FOUND_MESSAGE = "Failed to find diff line for: %{file_path}, old_line: %{old_line}, new_line: %{new_line}"
DIFF_FILE_NOT_FOUND_MESSAGE = "Failed to find diff file"
after_commit :create_diff_file, on: :create
 
def discussion_class(*)
Loading
Loading
@@ -33,7 +39,16 @@ class DiffNote < Note
return unless should_create_diff_file?
 
diff_file = fetch_diff_file
raise NoteDiffFileCreationError, DIFF_FILE_NOT_FOUND_MESSAGE unless diff_file
diff_line = diff_file.line_for_position(self.original_position)
unless diff_line
raise NoteDiffFileCreationError, DIFF_LINE_NOT_FOUND_MESSAGE % {
file_path: diff_file.file_path,
old_line: original_position.old_line,
new_line: original_position.new_line
}
end
 
creation_params = diff_file.diff.to_hash
.except(:too_large)
Loading
Loading
@@ -110,19 +125,20 @@ class DiffNote < Note
def fetch_diff_file
return note_diff_file.raw_diff_file if note_diff_file
 
file =
if created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
# presenting a "current version" of the MR discussion diff.
# So no need to make an extra Gitaly diff request for it.
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
noteable.diffs(original_position.diff_options).diff_files.first
else
original_position.diff_file(repository)
end
if created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're
# presenting a "current version" of the MR discussion diff.
# So no need to make an extra Gitaly diff request for it.
# As an extra benefit, the returned `diff_file` already
# has `highlighted_diff_lines` data set from Redis on
# `Diff::FileCollection::MergeRequestDiff`.
file = noteable.diffs(original_position.diff_options).diff_files.first
# if line is not found in persisted diffs, fallback and retrieve file from repository using gitaly
# This is required because of https://gitlab.com/gitlab-org/gitlab/issues/42676
file = nil if file&.line_for_position(original_position).nil? && importing?
end
 
file ||= original_position.diff_file(repository)
file&.unfold_diff_lines(position)
 
file
Loading
Loading
Loading
Loading
@@ -14,7 +14,7 @@ class BuildArtifactEntity < Grape::Entity
download_project_job_artifacts_path(project, job)
end
 
expose :keep_path, if: -> (*) { job.has_expiring_artifacts? } do |job|
expose :keep_path, if: -> (*) { job.has_expiring_archive_artifacts? } do |job|
keep_project_job_artifacts_path(project, job)
end
 
Loading
Loading
Loading
Loading
@@ -31,7 +31,7 @@ class BuildDetailsEntity < JobEntity
browse_project_job_artifacts_path(project, build)
end
 
expose :keep_path, if: -> (*) { build.has_expiring_artifacts? && can?(current_user, :update_build, build) } do |build|
expose :keep_path, if: -> (*) { build.has_expiring_archive_artifacts? && can?(current_user, :update_build, build) } do |build|
keep_project_job_artifacts_path(project, build)
end
 
Loading
Loading
.js-autodevops-banner.banner-callout.banner-non-empty-state.append-bottom-20.prepend-top-10{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
.banner-graphic
= custom_icon('icon_autodevops')
%section.js-autodevops-banner.gl-banner{ data: { uid: 'auto_devops_settings_dismissed', project_path: project_path(@project) } }
.gl-banner-illustration
= image_tag('illustrations/autodevops.svg')
 
.banner-body.prepend-left-10.append-bottom-10
%h5.banner-title= s_('AutoDevOps|Auto DevOps')
.gl-banner-content
%h1.gl-banner-title= s_('AutoDevOps|Auto DevOps')
%p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p
- link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
= s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
.banner-buttons
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), class: 'btn js-close-callout'
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), class: 'btn btn-md new-gl-button js-close-callout'
 
%button.btn-transparent.banner-close.close.js-close-callout{ type: 'button',
%button.gl-banner-close.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss Auto DevOps box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
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