Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/commit/image_file.js3
-rw-r--r--app/assets/javascripts/due_date_select.js4
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue238
-rw-r--r--app/assets/javascripts/filterable_list.js2
-rw-r--r--app/assets/javascripts/labels_select.js5
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/milestone_select.js7
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue1
-rw-r--r--app/assets/javascripts/users_select.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue20
-rw-r--r--app/assets/stylesheets/pages/error_list.scss69
-rw-r--r--app/models/blob.rb1
-rw-r--r--app/models/blob_viewer/cargo_toml.rb17
-rw-r--r--app/models/ci/build.rb8
-rw-r--r--app/models/diff_note.rb40
-rw-r--r--app/serializers/build_artifact_entity.rb2
-rw-r--r--app/serializers/build_details_entity.rb2
-rw-r--r--app/views/shared/_auto_devops_callout.html.haml15
18 files changed, 314 insertions, 124 deletions
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index a28e17f7a56..fb8b1c17407 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -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);
});
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 3c650397a19..b973316b3b9 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -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();
};
@@ -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) {
@@ -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();
});
}
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index 8e2128ac713..1042029b6db 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -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'),
@@ -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
@@ -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
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index c21fba06d42..be2eee828ff 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -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;
@@ -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);
}
}
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 6abf723be9a..f57febbda37 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -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
@@ -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
@@ -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();
@@ -361,6 +364,7 @@ export default class LabelsSelect {
const label = clickEvent.selectedObj;
const fadeOutLoader = () => {
+ // eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
};
@@ -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;
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 674415c9d01..8373ddd7a43 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -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();
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 1738dbe439c..d15e4ecb537 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -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 =
@@ -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 {
@@ -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', '');
@@ -247,6 +253,7 @@ export default class MilestoneSelect {
}
})
.catch(() => {
+ // eslint-disable-next-line no-jquery/no-fade
$loading.fadeOut();
});
}
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 492d8de3802..4ca32b9b005 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -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"
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index bf2880aca9c..6d7d863f273 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -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');
@@ -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');
@@ -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 = {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 326440f5013..4f5f3ee5cf9 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -20,6 +20,11 @@ export default {
Suggestions,
},
props: {
+ isSubmitting: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
markdownPreviewPath: {
type: String,
required: false,
@@ -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
@@ -177,7 +196,6 @@ export default {
this.renderMarkdown();
}
},
-
showWriteTab() {
this.markdownPreview = '';
this.previewMarkdown = false;
diff --git a/app/assets/stylesheets/pages/error_list.scss b/app/assets/stylesheets/pages/error_list.scss
new file mode 100644
index 00000000000..f97953ce824
--- /dev/null
+++ b/app/assets/stylesheets/pages/error_list.scss
@@ -0,0 +1,69 @@
+$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;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 0a425f2b961..42ee00bc196 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -51,6 +51,7 @@ class Blob < SimpleDelegator
BlobViewer::Contributing,
BlobViewer::Changelog,
+ BlobViewer::CargoToml,
BlobViewer::Cartfile,
BlobViewer::ComposerJson,
BlobViewer::Gemfile,
diff --git a/app/models/blob_viewer/cargo_toml.rb b/app/models/blob_viewer/cargo_toml.rb
new file mode 100644
index 00000000000..2f1ebd25b4f
--- /dev/null
+++ b/app/models/blob_viewer/cargo_toml.rb
@@ -0,0 +1,17 @@
+# 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
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 7e7c580a48e..e3f0e07bb8f 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -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!
@@ -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
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 686d06d3ee0..939d8bc4bef 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -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(*)
@@ -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)
@@ -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
diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb
index 414f436e76e..c173c155edf 100644
--- a/app/serializers/build_artifact_entity.rb
+++ b/app/serializers/build_artifact_entity.rb
@@ -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
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 480a8cab6ff..0ef71ff8af5 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -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
diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml
index 084d295f2c1..128508e954e 100644
--- a/app/views/shared/_auto_devops_callout.html.haml
+++ b/app/views/shared/_auto_devops_callout.html.haml
@@ -1,16 +1,15 @@
-.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')