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
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.yml1
-rw-r--r--Gemfile2
-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
-rw-r--r--changelogs/unreleased/33892-add-conan-recipe-to-package-details.yml5
-rw-r--r--changelogs/unreleased/35527-add-created-at-to-package.yml5
-rw-r--r--changelogs/unreleased/36410-error-list-mobile.yml5
-rw-r--r--changelogs/unreleased/37682-fix-diff-file-creation.yml5
-rw-r--r--changelogs/unreleased/dont_run_auto_devops.yml5
-rw-r--r--changelogs/unreleased/eb-no-keep-report-artifacts.yml5
-rw-r--r--changelogs/unreleased/feat-rust-cargo-toml-blob-view.yml5
-rw-r--r--config/webpack.config.js2
-rw-r--r--doc/api/packages.md15
-rw-r--r--doc/ci/README.md1
-rw-r--r--doc/ci/cloud_deployment/index.md46
-rw-r--r--doc/user/admin_area/index.md33
-rw-r--r--doc/user/project/clusters/add_remove_clusters.md6
-rw-r--r--doc/user/project/members/index.md2
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/content/auto_devops.rb18
-rw-r--r--lib/gitlab/ci/pipeline/chain/config/content/legacy_auto_devops.rb18
-rw-r--r--lib/gitlab/ci/templates/Beta/Auto-DevOps.gitlab-ci.yml163
-rw-r--r--lib/gitlab/dependency_linker.rb3
-rw-r--r--lib/gitlab/dependency_linker/cargo_toml_linker.rb46
-rw-r--r--lib/gitlab/file_detector.rb1
-rw-r--r--lib/sentry/client.rb54
-rw-r--r--lib/sentry/client/issue.rb62
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/fixtures/sentry/issue_sample_response.json311
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js4
-rw-r--r--spec/frontend/fixtures/static/mock-video.mp4bin0 -> 383631 bytes
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js44
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb73
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb50
-rw-r--r--spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb69
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb8
-rw-r--r--spec/lib/sentry/client/issue_spec.rb79
-rw-r--r--spec/lib/sentry/client_spec.rb12
-rw-r--r--spec/models/ci/build_spec.rb18
-rw-r--r--spec/models/diff_note_spec.rb120
-rw-r--r--spec/serializers/build_artifact_entity_spec.rb2
-rw-r--r--spec/serializers/build_details_entity_spec.rb22
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb1
-rw-r--r--spec/services/external_pull_requests/create_pipeline_service_spec.rb2
-rw-r--r--spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb46
60 files changed, 1570 insertions, 246 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index a8cbd9731a3..3c304e957b4 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -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
diff --git a/Gemfile b/Gemfile
index 8a5725b8e4b..212a6860480 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
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')
diff --git a/changelogs/unreleased/33892-add-conan-recipe-to-package-details.yml b/changelogs/unreleased/33892-add-conan-recipe-to-package-details.yml
new file mode 100644
index 00000000000..a3079f1c902
--- /dev/null
+++ b/changelogs/unreleased/33892-add-conan-recipe-to-package-details.yml
@@ -0,0 +1,5 @@
+---
+title: Added Conan recipe in place of the package name on the package details page.
+merge_request: 21247
+author:
+type: changed
diff --git a/changelogs/unreleased/35527-add-created-at-to-package.yml b/changelogs/unreleased/35527-add-created-at-to-package.yml
new file mode 100644
index 00000000000..fa127f8b4b2
--- /dev/null
+++ b/changelogs/unreleased/35527-add-created-at-to-package.yml
@@ -0,0 +1,5 @@
+---
+title: Adds created_at object to package api response
+merge_request: 20816
+author:
+type: added
diff --git a/changelogs/unreleased/36410-error-list-mobile.yml b/changelogs/unreleased/36410-error-list-mobile.yml
new file mode 100644
index 00000000000..759fe4ed76d
--- /dev/null
+++ b/changelogs/unreleased/36410-error-list-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Improve error list UI on mobile viewports
+merge_request: 21192
+author:
+type: added
diff --git a/changelogs/unreleased/37682-fix-diff-file-creation.yml b/changelogs/unreleased/37682-fix-diff-file-creation.yml
new file mode 100644
index 00000000000..fb9761dec98
--- /dev/null
+++ b/changelogs/unreleased/37682-fix-diff-file-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Add fallbacks and proper errors for diff file creation
+merge_request: 21034
+author:
+type: fixed
diff --git a/changelogs/unreleased/dont_run_auto_devops.yml b/changelogs/unreleased/dont_run_auto_devops.yml
new file mode 100644
index 00000000000..eb460e0538b
--- /dev/null
+++ b/changelogs/unreleased/dont_run_auto_devops.yml
@@ -0,0 +1,5 @@
+---
+title: Don't run Auto DevOps when no dockerfile or matching buildpack exists
+merge_request: 20267
+author:
+type: changed
diff --git a/changelogs/unreleased/eb-no-keep-report-artifacts.yml b/changelogs/unreleased/eb-no-keep-report-artifacts.yml
new file mode 100644
index 00000000000..c80fbd2c221
--- /dev/null
+++ b/changelogs/unreleased/eb-no-keep-report-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Remove keep button for non archive artifacts
+merge_request: 21553
+author:
+type: fixed
diff --git a/changelogs/unreleased/feat-rust-cargo-toml-blob-view.yml b/changelogs/unreleased/feat-rust-cargo-toml-blob-view.yml
new file mode 100644
index 00000000000..ccc5ea0fe7e
--- /dev/null
+++ b/changelogs/unreleased/feat-rust-cargo-toml-blob-view.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for Rust Cargo.toml dependency vizualisation and linking
+merge_request: 21374
+author: Fabio Huser
+type: added
diff --git a/config/webpack.config.js b/config/webpack.config.js
index d85fa84c32f..7da7e571d67 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -201,7 +201,7 @@ module.exports = {
loader: 'raw-loader',
},
{
- test: /\.(gif|png)$/,
+ test: /\.(gif|png|mp4)$/,
loader: 'url-loader',
options: { limit: 2048 },
},
diff --git a/doc/api/packages.md b/doc/api/packages.md
index 5b490b872da..afca7db97c8 100644
--- a/doc/api/packages.md
+++ b/doc/api/packages.md
@@ -31,13 +31,15 @@ Example response:
"id": 1,
"name": "com/mycompany/my-app",
"version": "1.0-SNAPSHOT",
- "package_type": "maven"
+ "package_type": "maven",
+ "created_at": "2019-11-27T03:37:38.711Z"
},
{
"id": 2,
"name": "@foo/bar",
"version": "1.0.3",
- "package_type": "npm"
+ "package_type": "npm",
+ "created_at": "2019-11-27T03:37:38.711Z"
}
]
```
@@ -76,7 +78,8 @@ Example response:
"_links": {
"web_path": "/namespace1/project1/-/packages/1",
"delete_api_path": "/namespace1/project1/-/packages/1"
- }
+ },
+ "created_at": "2019-11-27T03:37:38.711Z"
},
{
"id": 2,
@@ -86,7 +89,8 @@ Example response:
"_links": {
"web_path": "/namespace1/project1/-/packages/1",
"delete_api_path": "/namespace1/project1/-/packages/1"
- }
+ },
+ "created_at": "2019-11-27T03:37:38.711Z"
}
]
```
@@ -128,7 +132,8 @@ Example response:
"_links": {
"web_path": "/namespace1/project1/-/packages/1",
"delete_api_path": "/namespace1/project1/-/packages/1"
- }
+ },
+ "created_at": "2019-11-27T03:37:38.711Z"
}
```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index d1cf7e63c63..8a33298ea63 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -127,6 +127,7 @@ Its feature set is listed on the table below according to DevOps stages.
| [GitLab Pages](../user/project/pages/index.md) | Deploy static websites. |
| [GitLab Releases](../user/project/releases/index.md) | Add release notes to Git tags. |
| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes. |
+| [Cloud deployment](cloud_deployment/index.md) | Deploy your application to a main cloud provider. |
|---+---|
| **Secure** ||
| [Container Scanning](../user/application_security/container_scanning/index.md) **(ULTIMATE)** | Check your Docker containers for known vulnerabilities.|
diff --git a/doc/ci/cloud_deployment/index.md b/doc/ci/cloud_deployment/index.md
new file mode 100644
index 00000000000..f7dfe37da7a
--- /dev/null
+++ b/doc/ci/cloud_deployment/index.md
@@ -0,0 +1,46 @@
+---
+type: howto
+---
+
+# Cloud deployment
+
+Interacting with a major cloud provider such as Amazon AWS may have become a much needed task that's
+part of your delivery process. GitLab is making this process less painful by providing Docker images
+that come with the needed libraries and tools pre-installed.
+By referencing them in your CI/CD pipeline, you'll be able to interact with your chosen
+cloud provider more easily.
+
+## AWS
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/31167) in GitLab 12.6.
+
+GitLab's AWS Docker image provides the [AWS Command Line Interface](https://aws.amazon.com/cli/),
+which enables you to run `aws` commands. As part of your deployment strategy, you can run `aws` commands directly from
+`.gitlab-ci.yml` by specifying GitLab's AWS Docker image.
+
+Some credentials are required to be able to run `aws` commands:
+
+1. Sign up for [an AWS account](https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-set-up.html) if you don't have one yet.
+1. Log in onto the console and create [a new IAM user](https://console.aws.amazon.com/iam/home#/home).
+1. Select your newly created user to access its details. Navigate to **Security credentials > Create a new access key**.
+
+ NOTE: **Note:**
+ A new **Access key ID** and **Secret access key** pair will be generated. Please take a note of them right away.
+
+1. In your GitLab project, go to **Settings > CI / CD**. Set the Access key ID and Secret access key as [environment variables](../variables/README.md#gitlab-cicd-environment-variables), using the following variable names:
+
+ | Env. variable name | Value |
+ |:------------------------|:-------------------------|
+ | `AWS_ACCESS_KEY_ID` | Your "Access key ID" |
+ | `AWS_SECRET_ACCESS_KEY` | Your "Secret access key" |
+
+1. You can now use `aws` commands in the `.gitlab-ci.yml` file of this project:
+
+ ```yml
+ deploy:
+ stage: deploy
+ image: registry.gitlab.com/gitlab-org/cloud-deploy:latest
+ script:
+ - aws s3 ...
+ - aws create-deployment ...
+ ```
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index 35cb2b42c56..ccf20d797aa 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -18,22 +18,23 @@ Only admin users can access the Admin Area.
The Admin Area is made up of the following sections:
-| Section | Description |
-|:------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [Overview](#overview-section) | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administering-projects), [users](#administering-users), [groups](#administering-groups), [jobs](#administering-jobs), [Runners](#administering-runners), and [Gitaly servers](#administering-gitaly-servers). |
-| Monitoring | View GitLab [system information](#system-info), and information on [background jobs](#background-jobs), [logs](#logs), [health checks](monitoring/health_check.md), [requests profiles](#requests-profiles), and [audit logs](#audit-log-premium-only). |
-| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
-| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
-| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
-| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. |
-| License **(STARTER ONLY)** | Upload, display, and remove [licenses](license.md). |
-| Push Rules **(STARTER)** | Configure pre-defined Git [push rules](../../push_rules/push_rules.md) for projects. |
-| Geo **(PREMIUM ONLY)** | Configure and maintain [Geo nodes](geo_nodes.md). |
-| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). |
-| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. |
-| Labels | Create and maintain [labels](labels.md) for your GitLab instance. |
-| Appearance | Customize [GitLab's appearance](appearance.md). |
-| Settings | Modify the [settings](settings/index.md) for your GitLab instance. |
+| Section | Description |
+|:--------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Overview](#overview-section) | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administering-projects), [users](#administering-users), [groups](#administering-groups), [jobs](#administering-jobs), [Runners](#administering-runners), and [Gitaly servers](#administering-gitaly-servers). |
+| Monitoring | View GitLab [system information](#system-info), and information on [background jobs](#background-jobs), [logs](#logs), [health checks](monitoring/health_check.md), [requests profiles](#requests-profiles), and [audit logs](#audit-log-premium-only). |
+| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
+| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
+| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
+| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. |
+| License **(STARTER ONLY)** | Upload, display, and remove [licenses](license.md). |
+| Push Rules **(STARTER)** | Configure pre-defined Git [push rules](../../push_rules/push_rules.md) for projects. |
+| Geo **(PREMIUM ONLY)** | Configure and maintain [Geo nodes](geo_nodes.md). |
+| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). |
+| Credentials **(ULTIMATE ONLY)** | View [credentials](credentials_inventory.md) that can be used to access your instance. |
+| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. |
+| Labels | Create and maintain [labels](labels.md) for your GitLab instance. |
+| Appearance | Customize [GitLab's appearance](appearance.md). |
+| Settings | Modify the [settings](settings/index.md) for your GitLab instance. |
## Admin Dashboard
diff --git a/doc/user/project/clusters/add_remove_clusters.md b/doc/user/project/clusters/add_remove_clusters.md
index 6a0377f118d..270950ea44c 100644
--- a/doc/user/project/clusters/add_remove_clusters.md
+++ b/doc/user/project/clusters/add_remove_clusters.md
@@ -728,7 +728,11 @@ When removing the cluster integration, note:
- You need Maintainer [permissions](../../permissions.md) and above to remove a Kubernetes cluster
integration.
- When you remove a cluster, you only remove its relationship to GitLab, not the cluster itself. To
- remove the cluster, you can do so by visiting the GKE dashboard or using `kubectl`.
+ remove the cluster, you can do so by visiting the GKE or EKS dashboard, or using `kubectl`.
+
+[From GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/issues/26815), you can also remove all
+related GitLab cluster resources (for example, namespaces, roles, and bindings) when removing the
+integration.
## Learn more
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index c069882e38f..218bab26142 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -27,7 +27,7 @@ From the image above, we can deduce the following things:
- Administrator is the Owner and member of **all** groups and for that reason,
there is an indication of an ancestor group and inherited Owner permissions.
-[From](https://gitlab.com/gitlab-org/gitlab/issues/21727), you can filter this list
+[From GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/issues/21727), you can filter this list
using dropdown on the right side:
![Project members filter](img/project_members_filter_v12_6.png)
diff --git a/lib/gitlab/ci/pipeline/chain/config/content/auto_devops.rb b/lib/gitlab/ci/pipeline/chain/config/content/auto_devops.rb
index e9bcc67de9c..54be789988c 100644
--- a/lib/gitlab/ci/pipeline/chain/config/content/auto_devops.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/content/auto_devops.rb
@@ -11,7 +11,7 @@ module Gitlab
strong_memoize(:content) do
next unless project&.auto_devops_enabled?
- template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
+ template = Gitlab::Template::GitlabCiYmlTemplate.find(template_name)
YAML.dump('include' => [{ 'template' => template.full_name }])
end
end
@@ -19,6 +19,22 @@ module Gitlab
def source
:auto_devops_source
end
+
+ private
+
+ def template_name
+ if beta_enabled?
+ 'Beta/Auto-DevOps'
+ else
+ 'Auto-DevOps'
+ end
+ end
+
+ def beta_enabled?
+ Feature.enabled?(:auto_devops_beta, project, default_enabled: true) &&
+ # workflow:rules are required by `Beta/Auto-DevOps.gitlab-ci.yml`
+ Feature.enabled?(:workflow_rules, project, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/config/content/legacy_auto_devops.rb b/lib/gitlab/ci/pipeline/chain/config/content/legacy_auto_devops.rb
index c4cef356628..b282886a56f 100644
--- a/lib/gitlab/ci/pipeline/chain/config/content/legacy_auto_devops.rb
+++ b/lib/gitlab/ci/pipeline/chain/config/content/legacy_auto_devops.rb
@@ -11,7 +11,7 @@ module Gitlab
strong_memoize(:content) do
next unless project&.auto_devops_enabled?
- template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
+ template = Gitlab::Template::GitlabCiYmlTemplate.find(template_name)
template.content
end
end
@@ -19,6 +19,22 @@ module Gitlab
def source
:auto_devops_source
end
+
+ private
+
+ def template_name
+ if beta_enabled?
+ 'Beta/Auto-DevOps'
+ else
+ 'Auto-DevOps'
+ end
+ end
+
+ def beta_enabled?
+ Feature.enabled?(:auto_devops_beta, project, default_enabled: true) &&
+ # workflow:rules are required by `Beta/Auto-DevOps.gitlab-ci.yml`
+ Feature.enabled?(:workflow_rules, project, default_enabled: true)
+ end
end
end
end
diff --git a/lib/gitlab/ci/templates/Beta/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Beta/Auto-DevOps.gitlab-ci.yml
new file mode 100644
index 00000000000..2c5035705ac
--- /dev/null
+++ b/lib/gitlab/ci/templates/Beta/Auto-DevOps.gitlab-ci.yml
@@ -0,0 +1,163 @@
+# Auto DevOps - BETA do not use
+# This CI/CD configuration provides a standard pipeline for
+# * building a Docker image (using a buildpack if necessary),
+# * storing the image in the container registry,
+# * running tests from a buildpack,
+# * running code quality analysis,
+# * creating a review app for each topic branch,
+# * and continuous deployment to production
+#
+# Test jobs may be disabled by setting environment variables:
+# * test: TEST_DISABLED
+# * code_quality: CODE_QUALITY_DISABLED
+# * license_management: LICENSE_MANAGEMENT_DISABLED
+# * performance: PERFORMANCE_DISABLED
+# * sast: SAST_DISABLED
+# * dependency_scanning: DEPENDENCY_SCANNING_DISABLED
+# * container_scanning: CONTAINER_SCANNING_DISABLED
+# * dast: DAST_DISABLED
+# * review: REVIEW_DISABLED
+# * stop_review: REVIEW_DISABLED
+#
+# In order to deploy, you must have a Kubernetes cluster configured either
+# via a project integration, or via group/project variables.
+# KUBE_INGRESS_BASE_DOMAIN must also be set on the cluster settings,
+# as a variable at the group or project level, or manually added below.
+#
+# Continuous deployment to production is enabled by default.
+# If you want to deploy to staging first, set STAGING_ENABLED environment variable.
+# If you want to enable incremental rollout, either manual or time based,
+# set INCREMENTAL_ROLLOUT_MODE environment variable to "manual" or "timed".
+# If you want to use canary deployments, set CANARY_ENABLED environment variable.
+#
+# If Auto DevOps fails to detect the proper buildpack, or if you want to
+# specify a custom buildpack, set a project variable `BUILDPACK_URL` to the
+# repository URL of the buildpack.
+# e.g. BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-ruby.git#v142
+# If you need multiple buildpacks, add a file to your project called
+# `.buildpacks` that contains the URLs, one on each line, in order.
+# Note: Auto CI does not work with multiple buildpacks yet
+
+image: alpine:latest
+
+variables:
+ # KUBE_INGRESS_BASE_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
+ # KUBE_INGRESS_BASE_DOMAIN: domain.example.com
+
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: testing-password
+ POSTGRES_ENABLED: "true"
+ POSTGRES_DB: $CI_ENVIRONMENT_SLUG
+ POSTGRES_VERSION: 9.6.2
+
+ DOCKER_DRIVER: overlay2
+
+ ROLLOUT_RESOURCE_TYPE: deployment
+
+ DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501
+
+stages:
+ - build
+ - test
+ - deploy # dummy stage to follow the template guidelines
+ - review
+ - dast
+ - staging
+ - canary
+ - production
+ - incremental rollout 10%
+ - incremental rollout 25%
+ - incremental rollout 50%
+ - incremental rollout 100%
+ - performance
+ - cleanup
+
+workflow:
+ rules:
+ - if: '$BUILDPACK_URL || $AUTO_DEVOPS_EXPLICITLY_ENABLED == "1"'
+
+ - exists:
+ - Dockerfile
+
+ # https://github.com/heroku/heroku-buildpack-clojure
+ - exists:
+ - project.clj
+
+ # https://github.com/heroku/heroku-buildpack-go
+ - exists:
+ - go.mod
+ - Gopkg.mod
+ - Godeps/Godeps.json
+ - vendor/vendor.json
+ - glide.yaml
+ - src/**/*.go
+
+ # https://github.com/heroku/heroku-buildpack-gradle
+ - exists:
+ - gradlew
+ - build.gradle
+ - settings.gradle
+
+ # https://github.com/heroku/heroku-buildpack-java
+ - exists:
+ - pom.xml
+ - pom.atom
+ - pom.clj
+ - pom.groovy
+ - pom.rb
+ - pom.scala
+ - pom.yaml
+ - pom.yml
+
+ # https://github.com/heroku/heroku-buildpack-multi
+ - exists:
+ - .buildpacks
+
+ # https://github.com/heroku/heroku-buildpack-nodejs
+ - exists:
+ - package.json
+
+ # https://github.com/heroku/heroku-buildpack-php
+ - exists:
+ - composer.json
+ - index.php
+
+ # https://github.com/heroku/heroku-buildpack-play
+ # TODO: detect script excludes some scala files
+ - exists:
+ - '**/conf/application.conf'
+
+ # https://github.com/heroku/heroku-buildpack-python
+ # TODO: detect script checks that all of these exist, not any
+ - exists:
+ - requirements.txt
+ - setup.py
+ - Pipfile
+
+ # https://github.com/heroku/heroku-buildpack-ruby
+ - exists:
+ - Gemfile
+
+ # https://github.com/heroku/heroku-buildpack-scala
+ - exists:
+ - '*.sbt'
+ - project/*.scala
+ - .sbt/*.scala
+ - project/build.properties
+
+ # https://github.com/dokku/buildpack-nginx
+ - exists:
+ - .static
+
+include:
+ - template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
+ - template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
+ - template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+ - template: Jobs/Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+ - template: Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
+ - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
+ - template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+ - template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+ - template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+ - template: Security/License-Management.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
+ - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index c63d9e5bb71..7af380689d5 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -12,7 +12,8 @@ module Gitlab
PodspecJsonLinker,
CartfileLinker,
GodepsJsonLinker,
- RequirementsTxtLinker
+ RequirementsTxtLinker,
+ CargoTomlLinker
].freeze
def self.linker(blob_name)
diff --git a/lib/gitlab/dependency_linker/cargo_toml_linker.rb b/lib/gitlab/dependency_linker/cargo_toml_linker.rb
new file mode 100644
index 00000000000..57e0a5f4699
--- /dev/null
+++ b/lib/gitlab/dependency_linker/cargo_toml_linker.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DependencyLinker
+ class CargoTomlLinker < BaseLinker
+ self.file_type = :cargo_toml
+
+ def link
+ return highlighted_text unless toml
+
+ super
+ end
+
+ private
+
+ def link_dependencies
+ link_dependencies_at("dependencies")
+ link_dependencies_at("dev-dependencies")
+ link_dependencies_at("build-dependencies")
+ end
+
+ def link_dependencies_at(type)
+ dependencies = toml[type]
+ return unless dependencies
+
+ dependencies.each do |name, value|
+ link_toml(name, value, type) do |name|
+ "https://crates.io/crates/#{name}"
+ end
+ end
+ end
+
+ def link_toml(key, value, type, &url_proc)
+ if value.is_a? String
+ link_regex(/^(?<name>#{key})\s*=\s*"#{value}"/, &url_proc)
+ else
+ link_regex(/^\[#{type}\.(?<name>#{key})]/, &url_proc)
+ end
+ end
+
+ def toml
+ @toml ||= TomlRB.parse(plain_text) rescue nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb
index a386c21983d..234c834a83a 100644
--- a/lib/gitlab/file_detector.rb
+++ b/lib/gitlab/file_detector.rb
@@ -25,6 +25,7 @@ module Gitlab
route_map: '.gitlab/route-map.yml',
# Dependency files
+ cargo_toml: 'Cargo.toml',
cartfile: %r{\ACartfile[^/]*\z},
composer_json: 'composer.json',
gemfile: /\A(Gemfile|gems\.rb)\z/,
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 3df688a1fda..dd589b6194d 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -3,6 +3,7 @@
module Sentry
class Client
include Sentry::Client::Projects
+ include Sentry::Client::Issue
Error = Class.new(StandardError)
MissingKeysError = Class.new(StandardError)
@@ -23,12 +24,6 @@ module Sentry
@token = token
end
- def issue_details(issue_id:)
- issue = get_issue(issue_id: issue_id)
-
- map_to_detailed_error(issue)
- end
-
def issue_latest_event(issue_id:)
latest_event = get_issue_latest_event(issue_id: issue_id)
@@ -107,10 +102,6 @@ module Sentry
}.compact
end
- def get_issue(issue_id:)
- http_get(issue_api_url(issue_id))[:body]
- end
-
def get_issue_latest_event(issue_id:)
http_get(issue_latest_event_api_url(issue_id))[:body]
end
@@ -145,13 +136,6 @@ module Sentry
raise Client::Error, message
end
- def issue_api_url(issue_id)
- issue_url = URI(@url)
- issue_url.path = "/api/0/issues/#{issue_id}/"
-
- issue_url
- end
-
def issue_latest_event_api_url(issue_id)
latest_event_url = URI(@url)
latest_event_url.path = "/api/0/issues/#{issue_id}/events/latest/"
@@ -212,42 +196,6 @@ module Sentry
stack_trace_entry.dig('stacktrace', 'frames')
end
- def parse_gitlab_issue(plugin_issues)
- return unless plugin_issues
-
- gitlab_plugin = plugin_issues.detect { |item| item['id'] == 'gitlab' }
- return unless gitlab_plugin
-
- gitlab_plugin.dig('issue', 'url')
- end
-
- def map_to_detailed_error(issue)
- Gitlab::ErrorTracking::DetailedError.new(
- id: issue.fetch('id'),
- first_seen: issue.fetch('firstSeen', nil),
- last_seen: issue.fetch('lastSeen', nil),
- title: issue.fetch('title', nil),
- type: issue.fetch('type', nil),
- user_count: issue.fetch('userCount', nil),
- count: issue.fetch('count', nil),
- message: issue.dig('metadata', 'value'),
- culprit: issue.fetch('culprit', nil),
- external_url: issue_url(issue.fetch('id')),
- external_base_url: project_url,
- short_id: issue.fetch('shortId', nil),
- status: issue.fetch('status', nil),
- frequency: issue.dig('stats', '24h'),
- project_id: issue.dig('project', 'id'),
- project_name: issue.dig('project', 'name'),
- project_slug: issue.dig('project', 'slug'),
- gitlab_issue: parse_gitlab_issue(issue.fetch('pluginIssues', nil)),
- first_release_last_commit: issue.dig('firstRelease', 'lastCommit'),
- last_release_last_commit: issue.dig('lastRelease', 'lastCommit'),
- first_release_short_version: issue.dig('firstRelease', 'shortVersion'),
- last_release_short_version: issue.dig('lastRelease', 'shortVersion')
- )
- end
-
def map_to_error(issue)
Gitlab::ErrorTracking::Error.new(
id: issue.fetch('id'),
diff --git a/lib/sentry/client/issue.rb b/lib/sentry/client/issue.rb
new file mode 100644
index 00000000000..08ed5392a11
--- /dev/null
+++ b/lib/sentry/client/issue.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Sentry
+ class Client
+ module Issue
+ def issue_details(issue_id:)
+ issue = get_issue(issue_id: issue_id)
+
+ map_to_detailed_error(issue)
+ end
+
+ private
+
+ def get_issue(issue_id:)
+ http_get(issue_api_url(issue_id))[:body]
+ end
+
+ def issue_api_url(issue_id)
+ issue_url = URI(url)
+ issue_url.path = "/api/0/issues/#{CGI.escape(issue_id.to_s)}/"
+
+ issue_url
+ end
+
+ def parse_gitlab_issue(plugin_issues)
+ return unless plugin_issues
+
+ gitlab_plugin = plugin_issues.detect { |item| item['id'] == 'gitlab' }
+ return unless gitlab_plugin
+
+ gitlab_plugin.dig('issue', 'url')
+ end
+
+ def map_to_detailed_error(issue)
+ Gitlab::ErrorTracking::DetailedError.new(
+ id: issue.fetch('id'),
+ first_seen: issue.fetch('firstSeen', nil),
+ last_seen: issue.fetch('lastSeen', nil),
+ title: issue.fetch('title', nil),
+ type: issue.fetch('type', nil),
+ user_count: issue.fetch('userCount', nil),
+ count: issue.fetch('count', nil),
+ message: issue.dig('metadata', 'value'),
+ culprit: issue.fetch('culprit', nil),
+ external_url: issue_url(issue.fetch('id')),
+ external_base_url: project_url,
+ short_id: issue.fetch('shortId', nil),
+ status: issue.fetch('status', nil),
+ frequency: issue.dig('stats', '24h'),
+ project_id: issue.dig('project', 'id'),
+ project_name: issue.dig('project', 'name'),
+ project_slug: issue.dig('project', 'slug'),
+ gitlab_issue: parse_gitlab_issue(issue.fetch('pluginIssues', nil)),
+ first_release_last_commit: issue.dig('firstRelease', 'lastCommit'),
+ last_release_last_commit: issue.dig('lastRelease', 'lastCommit'),
+ first_release_short_version: issue.dig('firstRelease', 'shortVersion'),
+ last_release_short_version: issue.dig('lastRelease', 'shortVersion')
+ )
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index dee48529f60..3dbf9478be9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7795,6 +7795,9 @@ msgstr ""
msgid "Filter by milestone name"
msgstr ""
+msgid "Filter by name..."
+msgstr ""
+
msgid "Filter by two-factor authentication"
msgstr ""
@@ -11548,6 +11551,9 @@ msgstr ""
msgid "More actions"
msgstr ""
+msgid "More details"
+msgstr ""
+
msgid "More info"
msgstr ""
@@ -14726,6 +14732,9 @@ msgstr ""
msgid "Recent searches"
msgstr ""
+msgid "Recipe"
+msgstr ""
+
msgid "Recovery Codes"
msgstr ""
diff --git a/spec/fixtures/sentry/issue_sample_response.json b/spec/fixtures/sentry/issue_sample_response.json
new file mode 100644
index 00000000000..a320a21de34
--- /dev/null
+++ b/spec/fixtures/sentry/issue_sample_response.json
@@ -0,0 +1,311 @@
+{
+ "activity": [
+ {
+ "data": {},
+ "dateCreated": "2018-11-06T21:19:55Z",
+ "id": "0",
+ "type": "first_seen",
+ "user": null
+ }
+ ],
+ "annotations": [],
+ "assignedTo": null,
+ "count": "1",
+ "culprit": "raven.scripts.runner in main",
+ "firstRelease": {
+ "authors": [],
+ "commitCount": 0,
+ "data": {},
+ "dateCreated": "2018-11-06T21:19:55.146Z",
+ "dateReleased": null,
+ "deployCount": 0,
+ "firstEvent": "2018-11-06T21:19:55.271Z",
+ "lastCommit": null,
+ "lastDeploy": null,
+ "lastEvent": "2018-11-06T21:19:55.271Z",
+ "newGroups": 0,
+ "owner": null,
+ "projects": [
+ {
+ "name": "Pump Station",
+ "slug": "pump-station"
+ }
+ ],
+ "ref": null,
+ "shortVersion": "1764232",
+ "url": null,
+ "version": "17642328ead24b51867165985996d04b29310337"
+ },
+ "firstSeen": "2018-11-06T21:19:55Z",
+ "hasSeen": false,
+ "id": "503504",
+ "isBookmarked": false,
+ "isPublic": false,
+ "isSubscribed": true,
+ "lastRelease": null,
+ "lastSeen": "2018-11-06T21:19:55Z",
+ "level": "error",
+ "logger": null,
+ "metadata": {
+ "title": "This is an example Python exception"
+ },
+ "numComments": 0,
+ "participants": [],
+ "permalink": "https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/503504/",
+ "pluginActions": [],
+ "pluginContexts": [],
+ "pluginIssues": [
+ {
+ "id": "gitlab",
+ "issue": {
+ "url": "https://gitlab.com/gitlab-org/gitlab/issues/1"
+ }
+ }
+ ],
+ "project": {
+ "id": "2",
+ "name": "Pump Station",
+ "slug": "pump-station"
+ },
+ "seenBy": [],
+ "shareId": null,
+ "shortId": "PUMP-STATION-1",
+ "stats": {
+ "24h": [
+ [
+ 1541451600.0,
+ 557
+ ],
+ [
+ 1541455200.0,
+ 473
+ ],
+ [
+ 1541458800.0,
+ 914
+ ],
+ [
+ 1541462400.0,
+ 991
+ ],
+ [
+ 1541466000.0,
+ 925
+ ],
+ [
+ 1541469600.0,
+ 881
+ ],
+ [
+ 1541473200.0,
+ 182
+ ],
+ [
+ 1541476800.0,
+ 490
+ ],
+ [
+ 1541480400.0,
+ 820
+ ],
+ [
+ 1541484000.0,
+ 322
+ ],
+ [
+ 1541487600.0,
+ 836
+ ],
+ [
+ 1541491200.0,
+ 565
+ ],
+ [
+ 1541494800.0,
+ 758
+ ],
+ [
+ 1541498400.0,
+ 880
+ ],
+ [
+ 1541502000.0,
+ 677
+ ],
+ [
+ 1541505600.0,
+ 381
+ ],
+ [
+ 1541509200.0,
+ 814
+ ],
+ [
+ 1541512800.0,
+ 329
+ ],
+ [
+ 1541516400.0,
+ 446
+ ],
+ [
+ 1541520000.0,
+ 731
+ ],
+ [
+ 1541523600.0,
+ 111
+ ],
+ [
+ 1541527200.0,
+ 926
+ ],
+ [
+ 1541530800.0,
+ 772
+ ],
+ [
+ 1541534400.0,
+ 400
+ ],
+ [
+ 1541538000.0,
+ 943
+ ]
+ ],
+ "30d": [
+ [
+ 1538870400.0,
+ 565
+ ],
+ [
+ 1538956800.0,
+ 12862
+ ],
+ [
+ 1539043200.0,
+ 15617
+ ],
+ [
+ 1539129600.0,
+ 10809
+ ],
+ [
+ 1539216000.0,
+ 15065
+ ],
+ [
+ 1539302400.0,
+ 12927
+ ],
+ [
+ 1539388800.0,
+ 12994
+ ],
+ [
+ 1539475200.0,
+ 13139
+ ],
+ [
+ 1539561600.0,
+ 11838
+ ],
+ [
+ 1539648000.0,
+ 12088
+ ],
+ [
+ 1539734400.0,
+ 12338
+ ],
+ [
+ 1539820800.0,
+ 12768
+ ],
+ [
+ 1539907200.0,
+ 12816
+ ],
+ [
+ 1539993600.0,
+ 15356
+ ],
+ [
+ 1540080000.0,
+ 10910
+ ],
+ [
+ 1540166400.0,
+ 12306
+ ],
+ [
+ 1540252800.0,
+ 12912
+ ],
+ [
+ 1540339200.0,
+ 14700
+ ],
+ [
+ 1540425600.0,
+ 11890
+ ],
+ [
+ 1540512000.0,
+ 11684
+ ],
+ [
+ 1540598400.0,
+ 13510
+ ],
+ [
+ 1540684800.0,
+ 12625
+ ],
+ [
+ 1540771200.0,
+ 12811
+ ],
+ [
+ 1540857600.0,
+ 13180
+ ],
+ [
+ 1540944000.0,
+ 14651
+ ],
+ [
+ 1541030400.0,
+ 14161
+ ],
+ [
+ 1541116800.0,
+ 12612
+ ],
+ [
+ 1541203200.0,
+ 14316
+ ],
+ [
+ 1541289600.0,
+ 14742
+ ],
+ [
+ 1541376000.0,
+ 12505
+ ],
+ [
+ 1541462400.0,
+ 14180
+ ]
+ ]
+ },
+ "status": "unresolved",
+ "statusDetails": {},
+ "subscriptionDetails": null,
+ "tags": [],
+ "title": "This is an example Python exception",
+ "type": "default",
+ "userCount": 0,
+ "userReportCount": 0
+}
diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
index 581581405b6..d0893c0de01 100644
--- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -272,6 +272,7 @@ describe('ErrorTrackingList', () => {
describe('When pagination is not required', () => {
beforeEach(() => {
+ store.state.list.loading = false;
store.state.list.pagination = {};
mountComponent();
});
@@ -284,6 +285,7 @@ describe('ErrorTrackingList', () => {
describe('When pagination is required', () => {
describe('and the user is on the first page', () => {
beforeEach(() => {
+ store.state.list.loading = false;
mountComponent({ sync: false });
});
@@ -295,6 +297,7 @@ describe('ErrorTrackingList', () => {
describe('and the user is not on the first page', () => {
describe('and the previous button is clicked', () => {
beforeEach(() => {
+ store.state.list.loading = false;
mountComponent({ sync: false });
wrapper.setData({ pageValue: 2 });
});
@@ -313,6 +316,7 @@ describe('ErrorTrackingList', () => {
describe('and the next page button is clicked', () => {
beforeEach(() => {
+ store.state.list.loading = false;
mountComponent({ sync: false });
});
diff --git a/spec/frontend/fixtures/static/mock-video.mp4 b/spec/frontend/fixtures/static/mock-video.mp4
new file mode 100644
index 00000000000..1fc478842f5
--- /dev/null
+++ b/spec/frontend/fixtures/static/mock-video.mp4
Binary files differ
diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js
index 4cd0f62da0f..2569f2f02a7 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -1,9 +1,9 @@
import { mount, createLocalVue } from '@vue/test-utils';
-import { TEST_HOST } from 'spec/test_constants';
+import fieldComponent from '~/vue_shared/components/markdown/field.vue';
+import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
-import fieldComponent from '~/vue_shared/components/markdown/field.vue';
const markdownPreviewPath = `${TEST_HOST}/preview`;
const markdownDocsPath = `${TEST_HOST}/docs`;
@@ -19,6 +19,7 @@ function createComponent() {
propsData: {
markdownDocsPath,
markdownPreviewPath,
+ isSubmitting: false,
},
slots: {
textarea: '<textarea>testing\n123</textarea>',
@@ -27,6 +28,7 @@ function createComponent() {
<field-component
markdown-preview-path="${markdownPreviewPath}"
markdown-docs-path="${markdownDocsPath}"
+ :isSubmitting="false"
>
<textarea
slot="textarea"
@@ -44,6 +46,7 @@ const getPreviewLink = wrapper => wrapper.find('.nav-links .js-preview-link');
const getWriteLink = wrapper => wrapper.find('.nav-links .js-write-link');
const getMarkdownButton = wrapper => wrapper.find('.js-md');
const getAllMarkdownButtons = wrapper => wrapper.findAll('.js-md');
+const getVideo = wrapper => wrapper.find('video');
describe('Markdown field component', () => {
let axiosMock;
@@ -59,7 +62,10 @@ describe('Markdown field component', () => {
describe('mounted', () => {
let wrapper;
- const previewHTML = '<p>markdown preview</p>';
+ const previewHTML = `
+ <p>markdown preview</p>
+ <video src="${FIXTURES_PATH}/static/mock-video.mp4" muted="muted"></video>
+ `;
let previewLink;
let writeLink;
@@ -112,9 +118,35 @@ describe('Markdown field component', () => {
previewLink.trigger('click');
- setTimeout(() => {
- expect($.fn.renderGFM).toHaveBeenCalled();
- }, 0);
+ return axios.waitFor(markdownPreviewPath).then(() => {
+ expect(wrapper.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
+ });
+ });
+
+ it('calls video.pause() on comment input when isSubmitting is changed to true', () => {
+ wrapper = createComponent();
+ previewLink = getPreviewLink(wrapper);
+ previewLink.trigger('click');
+
+ let callPause;
+
+ return axios
+ .waitFor(markdownPreviewPath)
+ .then(() => {
+ const video = getVideo(wrapper);
+ callPause = jest.spyOn(video.element, 'pause').mockImplementation(() => true);
+
+ wrapper.setProps({
+ isSubmitting: true,
+ markdownPreviewPath,
+ markdownDocsPath,
+ });
+
+ return wrapper.vm.$nextTick();
+ })
+ .then(() => {
+ expect(callPause).toHaveBeenCalled();
+ });
});
it('clicking already active write or preview link does nothing', () => {
diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
index 7ebe5842fd0..f84f10bdc46 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb
@@ -40,7 +40,7 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
- template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
+ template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
@@ -52,7 +52,7 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
- template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
+ template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
@@ -82,12 +82,32 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
expect(project).to receive(:auto_devops_enabled?).and_return(true)
end
- it 'returns the content of AutoDevops template' do
- subject.perform!
+ context 'when beta is enabled' do
+ before do
+ stub_feature_flags(auto_devops_beta: true)
+ end
- expect(pipeline.config_source).to eq 'auto_devops_source'
- template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
- expect(command.config_content).to eq(template.content)
+ it 'returns the content of AutoDevops template' do
+ subject.perform!
+
+ expect(pipeline.config_source).to eq 'auto_devops_source'
+ template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
+ expect(command.config_content).to eq(template.content)
+ end
+ end
+
+ context 'when beta is disabled' do
+ before do
+ stub_feature_flags(auto_devops_beta: false)
+ end
+
+ it 'returns the content of AutoDevops template' do
+ subject.perform!
+
+ expect(pipeline.config_source).to eq 'auto_devops_source'
+ template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
+ expect(command.config_content).to eq(template.content)
+ end
end
end
@@ -190,15 +210,38 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
expect(project).to receive(:auto_devops_enabled?).and_return(true)
end
- it 'builds root config including the auto-devops template' do
- subject.perform!
+ context 'when beta is enabled' do
+ before do
+ stub_feature_flags(auto_devops_beta: true)
+ end
- expect(pipeline.config_source).to eq 'auto_devops_source'
- expect(command.config_content).to eq(<<~EOY)
- ---
- include:
- - template: Auto-DevOps.gitlab-ci.yml
- EOY
+ it 'builds root config including the auto-devops template' do
+ subject.perform!
+
+ expect(pipeline.config_source).to eq 'auto_devops_source'
+ expect(command.config_content).to eq(<<~EOY)
+ ---
+ include:
+ - template: Beta/Auto-DevOps.gitlab-ci.yml
+ EOY
+ end
+ end
+
+ context 'when beta is disabled' do
+ before do
+ stub_feature_flags(auto_devops_beta: false)
+ end
+
+ it 'builds root config including the auto-devops template' do
+ subject.perform!
+
+ expect(pipeline.config_source).to eq 'auto_devops_source'
+ expect(command.config_content).to eq(<<~EOY)
+ ---
+ include:
+ - template: Auto-DevOps.gitlab-ci.yml
+ EOY
+ end
end
end
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index c2f9930056a..12600d97b2f 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -9,7 +9,7 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
let(:user) { create(:admin) }
let(:default_branch) { 'master' }
let(:pipeline_branch) { default_branch }
- let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
+ let(:project) { create(:project, :auto_devops, :custom_repo, files: { 'README.md' => '' }) }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
let(:pipeline) { service.execute!(:push) }
let(:build_names) { pipeline.builds.pluck(:name) }
@@ -107,4 +107,52 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
end
end
end
+
+ describe 'build-pack detection' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:case_name, :files, :variables, :include_build_names, :not_include_build_names) do
+ 'No match' | { 'README.md' => '' } | {} | %w() | %w(build test)
+ 'Buildpack' | { 'README.md' => '' } | { 'BUILDPACK_URL' => 'http://example.com' } | %w(build test) | %w()
+ 'Explicit set' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '1' } | %w(build test) | %w()
+ 'Explicit unset' | { 'README.md' => '' } | { 'AUTO_DEVOPS_EXPLICITLY_ENABLED' => '0' } | %w() | %w(build test)
+ 'Dockerfile' | { 'Dockerfile' => '' } | {} | %w(build test) | %w()
+ 'Clojure' | { 'project.clj' => '' } | {} | %w(build test) | %w()
+ 'Go modules' | { 'go.mod' => '' } | {} | %w(build test) | %w()
+ 'Go gb' | { 'src/gitlab.com/gopackage.go' => '' } | {} | %w(build test) | %w()
+ 'Gradle' | { 'gradlew' => '' } | {} | %w(build test) | %w()
+ 'Java' | { 'pom.xml' => '' } | {} | %w(build test) | %w()
+ 'Multi-buildpack' | { '.buildpacks' => '' } | {} | %w(build test) | %w()
+ 'NodeJS' | { 'package.json' => '' } | {} | %w(build test) | %w()
+ 'PHP' | { 'composer.json' => '' } | {} | %w(build test) | %w()
+ 'Play' | { 'conf/application.conf' => '' } | {} | %w(build test) | %w()
+ 'Python' | { 'Pipfile' => '' } | {} | %w(build test) | %w()
+ 'Ruby' | { 'Gemfile' => '' } | {} | %w(build test) | %w()
+ 'Scala' | { 'build.sbt' => '' } | {} | %w(build test) | %w()
+ 'Static' | { '.static' => '' } | {} | %w(build test) | %w()
+ end
+
+ with_them do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps') }
+
+ let(:user) { create(:admin) }
+ let(:project) { create(:project, :custom_repo, files: files) }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: 'master' ) }
+ let(:pipeline) { service.execute(:push) }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow_any_instance_of(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
+ variables.each do |(key, value)|
+ create(:ci_variable, project: project, key: key, value: value)
+ end
+ end
+
+ it 'creates a pipeline with the expected jobs' do
+ expect(build_names).to include(*include_build_names)
+ expect(build_names).not_to include(*not_include_build_names)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
new file mode 100644
index 00000000000..86d5bc93bf7
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/cargo_toml_linker_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::DependencyLinker::CargoTomlLinker do
+ describe '.support?' do
+ it 'supports Cargo.toml' do
+ expect(described_class.support?('Cargo.toml')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('cargo.yaml')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Cargo.toml" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ # See https://doc.rust-lang.org/cargo/reference/manifest.html
+ [package]
+ # Package shouldn't be matched
+ name = "gitlab-test"
+ version = "0.0.1"
+ authors = ["Some User <some.user@example.org>"]
+ description = "A GitLab test Cargo.toml."
+ keywords = ["gitlab", "test", "rust", "crago"]
+ readme = "README.md"
+
+ [dependencies]
+ # Default dependencies format with fixed version and version range
+ chrono = "0.4.7"
+ xml-rs = ">=0.8.0"
+
+ [dependencies.memchr]
+ # Specific dependency with optional info
+ version = "2.2.1"
+ optional = true
+
+ [dev-dependencies]
+ # Dev dependency with version modifier
+ commandspec = "~0.12.2"
+
+ [build-dependencies]
+ # Build dependency with version wildcard
+ thread_local = "0.3.*"
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('chrono', 'https://crates.io/crates/chrono'))
+ expect(subject).to include(link('xml-rs', 'https://crates.io/crates/xml-rs'))
+ expect(subject).to include(link('memchr', 'https://crates.io/crates/memchr'))
+ expect(subject).to include(link('commandspec', 'https://crates.io/crates/commandspec'))
+ expect(subject).to include(link('thread_local', 'https://crates.io/crates/thread_local'))
+ end
+
+ it 'does not contain metadata identified as package' do
+ expect(subject).not_to include(link('version', 'https://crates.io/crates/version'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 3ea3334caf0..570a994f520 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -83,5 +83,13 @@ describe Gitlab::DependencyLinker do
described_class.link(blob_name, nil, nil)
end
+
+ it 'links using CargoTomlLinker' do
+ blob_name = 'Cargo.toml'
+
+ expect(described_class::CargoTomlLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
end
end
diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb
new file mode 100644
index 00000000000..17548e2081d
--- /dev/null
+++ b/spec/lib/sentry/client/issue_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Sentry::Client::Issue do
+ include SentryClientHelpers
+
+ let(:token) { 'test-token' }
+ let(:client) { Sentry::Client.new(sentry_url, token) }
+
+ describe '#issue_details' do
+ let(:issue_sample_response) do
+ Gitlab::Utils.deep_indifferent_access(
+ JSON.parse(fixture_file('sentry/issue_sample_response.json'))
+ )
+ end
+
+ let(:issue_id) { 503504 }
+ let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
+ let(:sentry_request_url) { "#{sentry_url}/issues/#{issue_id}/" }
+ let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: issue_sample_response) }
+
+ subject { client.issue_details(issue_id: issue_id) }
+
+ it_behaves_like 'calls sentry api'
+
+ it 'escapes issue ID' do
+ allow(CGI).to receive(:escape).and_call_original
+
+ subject
+
+ expect(CGI).to have_received(:escape).with(issue_id.to_s)
+ end
+
+ context 'error object created from sentry response' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:error_object, :sentry_response) do
+ :id | :id
+ :first_seen | :firstSeen
+ :last_seen | :lastSeen
+ :title | :title
+ :type | :type
+ :user_count | :userCount
+ :count | :count
+ :message | [:metadata, :value]
+ :culprit | :culprit
+ :short_id | :shortId
+ :status | :status
+ :frequency | [:stats, '24h']
+ :project_id | [:project, :id]
+ :project_name | [:project, :name]
+ :project_slug | [:project, :slug]
+ :first_release_last_commit | [:firstRelease, :lastCommit]
+ :last_release_last_commit | [:lastRelease, :lastCommit]
+ :first_release_short_version | [:firstRelease, :shortVersion]
+ :last_release_short_version | [:lastRelease, :shortVersion]
+ end
+
+ with_them do
+ it do
+ expect(subject.public_send(error_object)).to eq(issue_sample_response.dig(*sentry_response))
+ end
+ end
+
+ it 'has a correct external URL' do
+ expect(subject.external_url).to eq('https://sentrytest.gitlab.com/api/0/issues/503504')
+ end
+
+ it 'issue has a correct external base url' do
+ expect(subject.external_base_url).to eq('https://sentrytest.gitlab.com/api/0')
+ end
+
+ it 'has a correct GitLab issue url' do
+ expect(subject.gitlab_issue).to eq('https://gitlab.com/gitlab-org/gitlab/issues/1')
+ end
+ end
+ end
+end
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index cff06bf4a5f..8500f67b8e9 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -14,12 +14,6 @@ describe Sentry::Client do
}
end
- let(:issues_sample_response) do
- Gitlab::Utils.deep_indifferent_access(
- JSON.parse(fixture_file('sentry/issues_sample_response.json'))
- )
- end
-
subject(:client) { described_class.new(sentry_url, token) }
shared_examples 'issues has correct return type' do |klass|
@@ -33,6 +27,12 @@ describe Sentry::Client do
end
describe '#list_issues' do
+ let(:issues_sample_response) do
+ Gitlab::Utils.deep_indifferent_access(
+ JSON.parse(fixture_file('sentry/issues_sample_response.json'))
+ )
+ end
+
let(:issue_status) { 'unresolved' }
let(:limit) { 20 }
let(:search_term) { '' }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 900e0feaccc..20915e6d3b1 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2245,14 +2245,24 @@ describe Ci::Build do
end
end
- describe '#has_expiring_artifacts?' do
+ describe '#has_expiring_archive_artifacts?' do
context 'when artifacts have expiration date set' do
before do
build.update(artifacts_expire_at: 1.day.from_now)
end
- it 'has expiring artifacts' do
- expect(build).to have_expiring_artifacts
+ context 'and job artifacts file exists' do
+ let!(:archive) { create(:ci_job_artifact, :archive, job: build) }
+
+ it 'has expiring artifacts' do
+ expect(build).to have_expiring_archive_artifacts
+ end
+ end
+
+ context 'and job artifacts file does not exist' do
+ it 'does not have expiring artifacts' do
+ expect(build).not_to have_expiring_archive_artifacts
+ end
end
end
@@ -2262,7 +2272,7 @@ describe Ci::Build do
end
it 'does not have expiring artifacts' do
- expect(build).not_to have_expiring_artifacts
+ expect(build).not_to have_expiring_archive_artifacts
end
end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 601dac21e6a..ae493d5c081 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -91,18 +91,124 @@ describe DiffNote do
end
describe '#create_diff_file callback' do
- let(:noteable) { create(:merge_request) }
- let(:project) { noteable.project }
-
context 'merge request' do
- let!(:diff_note) { create(:diff_note_on_merge_request, project: project, noteable: noteable) }
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
- it 'creates a diff note file' do
- expect(diff_note.reload.note_diff_file).to be_present
+ subject { build(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
+
+ let(:diff_file_from_repository) do
+ position.diff_file(project.repository)
+ end
+
+ let(:diff_file) do
+ diffs = merge_request.diffs
+ raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['files/ruby/popen.rb'])).first
+ Gitlab::Diff::File.new(raw_diff,
+ repository: diffs.project.repository,
+ diff_refs: diffs.diff_refs,
+ fallback_diff_refs: diffs.fallback_diff_refs)
+ end
+
+ let(:diff_line) { diff_file.diff_lines.first }
+
+ let(:line_code) { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14' }
+
+ before do
+ allow(subject.position).to receive(:line_code).and_return('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14')
+ end
+
+ context 'when diffs are already created' do
+ before do
+ allow(subject).to receive(:created_at_diff?).and_return(true)
+ end
+
+ context 'when diff_file is found in persisted diffs' do
+ before do
+ allow(merge_request).to receive_message_chain(:diffs, :diff_files, :first).and_return(diff_file)
+ end
+
+ context 'when importing' do
+ before do
+ subject.importing = true
+ subject.line_code = line_code
+ end
+
+ context 'when diff_line is found in persisted diff_file' do
+ before do
+ allow(diff_file).to receive(:line_for_position).with(position).and_return(diff_line)
+ end
+
+ it 'creates a diff note file' do
+ subject.save
+ expect(subject.note_diff_file).to be_present
+ end
+ end
+
+ context 'when diff_line is not found in persisted diff_file' do
+ before do
+ allow(diff_file).to receive(:line_for_position).and_return(nil)
+ end
+
+ it_behaves_like 'a valid diff note with after commit callback'
+ end
+ end
+
+ context 'when not importing' do
+ context 'when diff_line is not found' do
+ before do
+ allow(diff_file).to receive(:line_for_position).with(position).and_return(nil)
+ end
+
+ it 'raises an error' do
+ expect { subject.save }.to raise_error(::DiffNote::NoteDiffFileCreationError,
+ "Failed to find diff line for: #{diff_file.file_path}, "\
+ "old_line: #{position.old_line}"\
+ ", new_line: #{position.new_line}")
+ end
+ end
+
+ context 'when diff_line is found' do
+ before do
+ allow(diff_file).to receive(:line_for_position).with(position).and_return(diff_line)
+ end
+
+ it 'creates a diff note file' do
+ subject.save
+ expect(subject.reload.note_diff_file).to be_present
+ end
+ end
+ end
+ end
+
+ context 'when diff file is not found in persisted diffs' do
+ before do
+ allow_next_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiff) do |merge_request_diff|
+ allow(merge_request_diff).to receive(:diff_files).and_return([])
+ end
+ end
+
+ it_behaves_like 'a valid diff note with after commit callback'
+ end
+ end
+
+ context 'when diffs are not already created' do
+ before do
+ allow(subject).to receive(:created_at_diff?).and_return(false)
+ end
+
+ it_behaves_like 'a valid diff note with after commit callback'
end
it 'does not create diff note file if it is a reply' do
- expect { create(:diff_note_on_merge_request, noteable: noteable, in_reply_to: diff_note) }
+ diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request)
+
+ expect { create(:diff_note_on_merge_request, noteable: merge_request, in_reply_to: diff_note) }
.not_to change(NoteDiffFile, :count)
end
end
diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb
index 09fe094fff1..5f1d5093e0a 100644
--- a/spec/serializers/build_artifact_entity_spec.rb
+++ b/spec/serializers/build_artifact_entity_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
describe BuildArtifactEntity do
let(:job) { create(:ci_build, name: 'test:job', artifacts_expire_at: 1.hour.from_now) }
+ let!(:archive) { create(:ci_job_artifact, :archive, job: job) }
+ let!(:metadata) { create(:ci_job_artifact, :metadata, job: job) }
let(:entity) do
described_class.new(job, request: double)
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index 91c5fd6bf2c..6fe9eaedfeb 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -176,5 +176,27 @@ describe BuildDetailsEntity do
expect(subject[:reports].first[:file_type]).to eq('codequality')
end
end
+
+ context 'when the build has no archive type artifacts' do
+ let!(:report) { create(:ci_job_artifact, :codequality, job: build) }
+
+ it 'does not expose any artifact actions path' do
+ expect(subject[:artifact].keys).not_to include(:download_path, :browse_path, :keep_path)
+ end
+ end
+
+ context 'when the build has archive type artifacts' do
+ let!(:report) { create(:ci_job_artifact, :codequality, job: build) }
+ let!(:archive) { create(:ci_job_artifact, :archive, job: build) }
+ let!(:metadata) { create(:ci_job_artifact, :metadata, job: build) }
+
+ before do
+ build.update(artifacts_expire_at: 7.days.from_now)
+ end
+
+ it 'exposes artifact details' do
+ expect(subject[:artifact].keys).to include(:download_path, :browse_path, :keep_path, :expire_at, :expired)
+ end
+ end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 04e57b1a2d4..5f760844046 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -493,6 +493,7 @@ describe Ci::CreatePipelineService do
before do
stub_ci_pipeline_yaml_file(nil)
allow_any_instance_of(Project).to receive(:auto_devops_enabled?).and_return(true)
+ create(:project_auto_devops, project: project)
end
it 'pull it from Auto-DevOps' do
diff --git a/spec/services/external_pull_requests/create_pipeline_service_spec.rb b/spec/services/external_pull_requests/create_pipeline_service_spec.rb
index a4da5b38b97..03481baea87 100644
--- a/spec/services/external_pull_requests/create_pipeline_service_spec.rb
+++ b/spec/services/external_pull_requests/create_pipeline_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
describe ExternalPullRequests::CreatePipelineService do
describe '#execute' do
- set(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :auto_devops, :repository) }
set(:user) { create(:user) }
let(:pull_request) { create(:external_pull_request, project: project) }
diff --git a/spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb b/spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb
new file mode 100644
index 00000000000..835d2dfe757
--- /dev/null
+++ b/spec/support/shared_examples/models/diff_note_after_commit_shared_examples.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+shared_examples 'a valid diff note with after commit callback' do
+ context 'when diff file is fetched from repository' do
+ before do
+ allow_any_instance_of(::Gitlab::Diff::Position).to receive(:diff_file).with(project.repository).and_return(diff_file_from_repository)
+ end
+
+ context 'when diff_line is not found' do
+ it 'raises an error' do
+ allow(diff_file_from_repository).to receive(:line_for_position).with(position).and_return(nil)
+
+ expect { subject.save }.to raise_error(::DiffNote::NoteDiffFileCreationError,
+ "Failed to find diff line for: #{diff_file_from_repository.file_path}, "\
+ "old_line: #{position.old_line}"\
+ ", new_line: #{position.new_line}")
+ end
+ end
+
+ context 'when diff_line is found' do
+ before do
+ allow(diff_file_from_repository).to receive(:line_for_position).with(position).and_return(diff_line)
+ end
+
+ it 'fallback to fetch file from repository' do
+ expect_any_instance_of(::Gitlab::Diff::Position).to receive(:diff_file).with(project.repository)
+
+ subject.save
+ end
+
+ it 'creates a diff note file' do
+ subject.save
+
+ expect(subject.reload.note_diff_file).to be_present
+ end
+ end
+ end
+
+ context 'when diff file is not found in repository' do
+ it 'raises an error' do
+ allow_any_instance_of(::Gitlab::Diff::Position).to receive(:diff_file).with(project.repository).and_return(nil)
+
+ expect { subject.save }.to raise_error(::DiffNote::NoteDiffFileCreationError, 'Failed to find diff file')
+ end
+ end
+end