summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--app/assets/javascripts/branches/components/divergence_graph.vue72
-rw-r--r--app/assets/javascripts/branches/components/graph_bar.vue69
-rw-r--r--app/assets/javascripts/branches/constants.js6
-rw-r--r--app/assets/javascripts/branches/divergence_graph.js23
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue77
-rw-r--r--app/assets/javascripts/groups/components/item_actions.vue28
-rw-r--r--app/assets/javascripts/groups/components/item_caret.vue2
-rw-r--r--app/assets/javascripts/groups/components/item_stats.vue8
-rw-r--r--app/assets/javascripts/groups/components/item_type_icon.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue8
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue5
-rw-r--r--app/assets/javascripts/monitoring/components/charts/column.vue131
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue2
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js2
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination/constants.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/image.vue21
-rw-r--r--app/assets/stylesheets/components/popover.scss6
-rw-r--r--app/assets/stylesheets/framework/lists.scss1
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/pages/branches.scss36
-rw-r--r--app/assets/stylesheets/pages/groups.scss80
-rw-r--r--app/assets/stylesheets/pages/projects.scss5
-rw-r--r--app/controllers/dashboard/todos_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb8
-rw-r--r--app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb3
-rw-r--r--app/finders/environments_finder.rb29
-rw-r--r--app/helpers/appearances_helper.rb5
-rw-r--r--app/helpers/issues_helper.rb14
-rw-r--r--app/helpers/todos_helper.rb2
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/models/ci/pipeline_schedule.rb23
-rw-r--r--app/models/clusters/platforms/kubernetes.rb9
-rw-r--r--app/models/concerns/service_push_data_validations.rb43
-rw-r--r--app/models/deployment.rb2
-rw-r--r--app/models/environment.rb4
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/merge_request.rb36
-rw-r--r--app/models/pages_domain.rb10
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_services/drone_ci_service.rb20
-rw-r--r--app/models/project_services/teamcity_service.rb74
-rw-r--r--app/models/todo.rb2
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb3
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb20
-rw-r--r--app/services/merge_requests/mergeability_check_service.rb120
-rw-r--r--app/services/service_response.rb15
-rw-r--r--app/views/layouts/header/_new_dropdown.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml16
-rw-r--r--app/views/projects/branches/_branch.html.haml21
-rw-r--r--app/views/projects/commits/_commit.html.haml4
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml8
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/services/_form.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/workers/all_queues.yml2
-rw-r--r--app/workers/build_success_worker.rb12
-rw-r--r--app/workers/cluster_provision_worker.rb2
-rw-r--r--app/workers/pages_domain_ssl_renewal_cron_worker.rb14
-rw-r--r--app/workers/pages_domain_ssl_renewal_worker.rb15
-rw-r--r--changelogs/add-name-parameter-to-project-environments-api.yml5
-rw-r--r--changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml5
-rw-r--r--changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml5
-rw-r--r--changelogs/unreleased/52366-improved-group-lists-ui.yml5
-rw-r--r--changelogs/unreleased/60617-enable-project-cluster-jit.yml5
-rw-r--r--changelogs/unreleased/63559-remove-avatar-from-sign-in.yml5
-rw-r--r--changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml5
-rw-r--r--changelogs/unreleased/fix-labels-in-hooks.yml5
-rw-r--r--changelogs/unreleased/fix-notes-emails-with-group-settings.yml5
-rw-r--r--changelogs/unreleased/fix-pipeline-schedule-edge-case.yml6
-rw-r--r--changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml5
-rw-r--r--changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml5
-rw-r--r--changelogs/unreleased/search-blob-basenames.yml5
-rw-r--r--changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-todos-controller.yml5
-rw-r--r--changelogs/unreleased/sh-strong-memoize-appearances.yml5
-rw-r--r--changelogs/unreleased/update-pagination-texts.yml5
-rw-r--r--changelogs/unreleased/update-tar-to-2-2-2.yml5
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--db/migrate/20190607145325_add_pages_domains_ssl_renew_index.rb25
-rw-r--r--db/post_migrate/20190620112608_enqueue_reset_merge_status_second_run.rb25
-rw-r--r--db/schema.rb3
-rw-r--r--doc/api/environments.md4
-rw-r--r--doc/api/merge_requests.md20
-rw-r--r--doc/ci/variables/README.md22
-rw-r--r--doc/development/code_review.md3
-rw-r--r--doc/development/profiling.md4
-rw-r--r--doc/development/reusing_abstractions.md36
-rw-r--r--doc/user/admin_area/settings/visibility_and_access_controls.md5
-rw-r--r--doc/user/group/index.md7
-rw-r--r--doc/user/project/clusters/index.md4
-rw-r--r--doc/user/project/deploy_boards.md5
-rw-r--r--lib/api/branches.rb5
-rw-r--r--lib/api/commits.rb5
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/environments.rb7
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/merge_requests.rb24
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/gitlab/ci/ansi2html.rb4
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb1
-rw-r--r--lib/gitlab/data_builder/note.rb2
-rw-r--r--lib/gitlab/hook_data/issue_builder.rb2
-rw-r--r--lib/gitlab/search/found_blob.rb9
-rw-r--r--locale/gitlab.pot18
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/project/pipeline/index.rb2
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb28
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb2
-rw-r--r--spec/factories/pages_domains.rb1
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb13
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb1
-rw-r--r--spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb6
-rw-r--r--spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb8
-rw-r--r--spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap37
-rw-r--r--spec/frontend/branches/components/divergence_graph_spec.js67
-rw-r--r--spec/frontend/branches/components/graph_bar_spec.js89
-rw-r--r--spec/helpers/appearances_helper_spec.rb16
-rw-r--r--spec/javascripts/groups/components/group_item_spec.js7
-rw-r--r--spec/javascripts/groups/components/item_stats_spec.js12
-rw-r--r--spec/javascripts/monitoring/charts/column_spec.js58
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js4
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb90
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb10
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb19
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/search/found_blob_spec.rb39
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb30
-rw-r--r--spec/migrations/enqueue_reset_merge_status_second_run_spec.rb52
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb51
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb23
-rw-r--r--spec/models/issue_spec.rb9
-rw-r--r--spec/models/merge_request_spec.rb87
-rw-r--r--spec/models/pages_domain_spec.rb26
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb94
-rw-r--r--spec/requests/api/branches_spec.rb10
-rw-r--r--spec/requests/api/commits_spec.rb14
-rw-r--r--spec/requests/api/environments_spec.rb41
-rw-r--r--spec/requests/api/merge_requests_spec.rb80
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb10
-rw-r--r--spec/services/clusters/update_service_spec.rb1
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb47
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb285
-rw-r--r--spec/services/service_response_spec.rb16
-rw-r--r--spec/support/helpers/email_helpers.rb15
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb16
-rw-r--r--spec/workers/build_success_worker_spec.rb44
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb7
-rw-r--r--spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb59
-rw-r--r--spec/workers/pages_domain_ssl_renewal_worker_spec.rb35
-rw-r--r--yarn.lock24
163 files changed, 2287 insertions, 829 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8922c5b4938..118034867ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 12.0.1 (2019-06-24)
+
+- No changes.
+
## 12.0.0 (2019-06-22)
### Security (10 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 21998d3c2d9..9db5ea12f52 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.47.0
+1.48.0
diff --git a/Gemfile b/Gemfile
index b9ce2422153..302f39756e8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -300,6 +300,9 @@ gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0'
+# Memory benchmarks
+gem 'derailed_benchmarks', require: false
+
# Metrics
group :metrics do
gem 'method_source', '~> 0.8', require: false
@@ -374,7 +377,6 @@ group :development, :test do
gem 'activerecord_sane_schema_dumper', '1.0'
gem 'stackprof', '~> 0.2.10', require: false
- gem 'derailed_benchmarks', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false
diff --git a/app/assets/javascripts/branches/components/divergence_graph.vue b/app/assets/javascripts/branches/components/divergence_graph.vue
new file mode 100644
index 00000000000..36fff370ea1
--- /dev/null
+++ b/app/assets/javascripts/branches/components/divergence_graph.vue
@@ -0,0 +1,72 @@
+<script>
+import { sprintf, __ } from '~/locale';
+import GraphBar from './graph_bar.vue';
+import { MAX_COMMIT_COUNT } from '../constants';
+
+export default {
+ components: {
+ GraphBar,
+ },
+ props: {
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
+ distance: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ aheadCount: {
+ type: Number,
+ required: true,
+ },
+ behindCount: {
+ type: Number,
+ required: true,
+ },
+ maxCommits: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ title() {
+ if (this.distance) {
+ return sprintf(
+ __('More than %{number_commits_distance} commits different with %{default_branch}'),
+ {
+ number_commits_distance:
+ this.distance >= MAX_COMMIT_COUNT ? `${MAX_COMMIT_COUNT - 1}+` : this.distance,
+ default_branch: this.defaultBranch,
+ },
+ );
+ }
+
+ return sprintf(
+ __(
+ '%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead',
+ ),
+ {
+ number_commits_behind: this.behindCount,
+ number_commits_ahead: this.aheadCount,
+ default_branch: this.defaultBranch,
+ },
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <div :title="title" class="divergence-graph px-2 d-none d-md-block">
+ <template v-if="distance">
+ <graph-bar :count="distance" :max-commits="maxCommits" position="full" />
+ </template>
+ <template v-else>
+ <graph-bar :count="behindCount" :max-commits="maxCommits" position="left" />
+ <div class="graph-separator pull-left mt-1"></div>
+ <graph-bar :count="aheadCount" :max-commits="maxCommits" position="right" />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/branches/components/graph_bar.vue b/app/assets/javascripts/branches/components/graph_bar.vue
new file mode 100644
index 00000000000..83da41ca097
--- /dev/null
+++ b/app/assets/javascripts/branches/components/graph_bar.vue
@@ -0,0 +1,69 @@
+<script>
+import { SIDES, MAX_COMMIT_COUNT } from '../constants';
+
+export default {
+ props: {
+ position: {
+ type: String,
+ required: true,
+ },
+ count: {
+ type: Number,
+ required: true,
+ },
+ maxCommits: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ label() {
+ if (this.count >= MAX_COMMIT_COUNT) {
+ return `${MAX_COMMIT_COUNT - 1}+`;
+ }
+
+ return this.count;
+ },
+ barGraphWidthFactor() {
+ return this.maxCommits > 0 ? 100 / this.maxCommits : 0;
+ },
+ style() {
+ return {
+ width: `${this.count * this.barGraphWidthFactor}%`,
+ };
+ },
+ isFullWidth() {
+ return this.position === SIDES.full;
+ },
+ isLeftSide() {
+ return this.position === SIDES.left;
+ },
+ roundedClass() {
+ if (this.isFullWidth) return 'rounded';
+
+ return `rounded-${this.position}`;
+ },
+ textAlignmentClass() {
+ if (this.isFullWidth) return 'text-center';
+
+ return `text-${this.isLeftSide ? SIDES.right : SIDES.left}`;
+ },
+ positionSideClass() {
+ return `position-${this.isLeftSide ? SIDES.right : SIDES.left}-0`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div :class="{ full: isFullWidth }" class="position-relative pull-left pt-1 graph-side h-100">
+ <div
+ :style="style"
+ :class="[roundedClass, positionSideClass]"
+ class="position-absolute bar js-graph-bar"
+ ></div>
+ <span :class="textAlignmentClass" class="d-block pt-1 pr-1 count js-graph-count">
+ {{ label }}
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/branches/constants.js b/app/assets/javascripts/branches/constants.js
new file mode 100644
index 00000000000..16949e662d2
--- /dev/null
+++ b/app/assets/javascripts/branches/constants.js
@@ -0,0 +1,6 @@
+export const SIDES = {
+ full: 'full',
+ left: 'left',
+ right: 'right',
+};
+export const MAX_COMMIT_COUNT = 1000;
diff --git a/app/assets/javascripts/branches/divergence_graph.js b/app/assets/javascripts/branches/divergence_graph.js
new file mode 100644
index 00000000000..670e8e9eb60
--- /dev/null
+++ b/app/assets/javascripts/branches/divergence_graph.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import DivergenceGraph from './components/divergence_graph.vue';
+
+export default () => {
+ document.querySelectorAll('.js-branch-divergence-graph').forEach(el => {
+ const { distance, aheadCount, behindCount, defaultBranch, maxCommits } = el.dataset;
+
+ return new Vue({
+ el,
+ render(h) {
+ return h(DivergenceGraph, {
+ props: {
+ defaultBranch,
+ distance: distance ? parseInt(distance, 10) : null,
+ aheadCount: parseInt(aheadCount, 10),
+ behindCount: parseInt(behindCount, 10),
+ maxCommits: parseInt(maxCommits, 10),
+ },
+ });
+ },
+ });
+ });
+};
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index d5130cd331d..9909f437fc8 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -1,12 +1,15 @@
<script>
+import { GlLoadingIcon } from '@gitlab/ui';
import { visitUrl } from '../../lib/utils/url_utility';
import tooltip from '../../vue_shared/directives/tooltip';
import identicon from '../../vue_shared/components/identicon.vue';
import eventHub from '../event_hub';
+import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '../constants';
import itemCaret from './item_caret.vue';
import itemTypeIcon from './item_type_icon.vue';
import itemStats from './item_stats.vue';
+import itemStatsValue from './item_stats_value.vue';
import itemActions from './item_actions.vue';
export default {
@@ -14,10 +17,12 @@ export default {
tooltip,
},
components: {
+ GlLoadingIcon,
identicon,
itemCaret,
itemTypeIcon,
itemStats,
+ itemStatsValue,
itemActions,
},
props: {
@@ -57,6 +62,12 @@ export default {
isGroup() {
return this.group.type === 'group';
},
+ visibilityIcon() {
+ return VISIBILITY_TYPE_ICON[this.group.visibility];
+ },
+ visibilityTooltip() {
+ return GROUP_VISIBILITY_TYPE[this.group.visibility];
+ },
},
methods: {
onClickRowGroup(e) {
@@ -80,43 +91,61 @@ export default {
<li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup">
<div
:class="{ 'project-row-contents': !isGroup }"
- class="group-row-contents d-flex justify-content-end align-items-center"
+ class="group-row-contents d-flex align-items-center"
>
<div class="folder-toggle-wrap append-right-4 d-flex align-items-center">
<item-caret :is-group-open="group.isOpen" />
<item-type-icon :item-type="group.type" :is-group-open="group.isOpen" />
</div>
+ <gl-loading-icon
+ v-if="group.isChildrenLoading"
+ size="md"
+ class="d-none d-sm-inline-flex flex-shrink-0 append-right-10"
+ />
<div
- :class="{ 'content-loading': group.isChildrenLoading }"
- class="avatar-container rect-avatar s24 d-none d-sm-flex"
+ :class="{ 'd-sm-flex': !group.isChildrenLoading }"
+ class="avatar-container rect-avatar s32 d-none flex-grow-0 flex-shrink-0 "
>
<a :href="group.relativePath" class="no-expand">
- <img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s24" />
- <identicon v-else :entity-id="group.id" :entity-name="group.name" size-class="s24" />
+ <img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s32" />
+ <identicon v-else :entity-id="group.id" :entity-name="group.name" size-class="s32" />
</a>
</div>
- <div class="group-text flex-grow">
- <div class="title namespace-title append-right-8">
- <a
- v-tooltip
- :href="group.relativePath"
- :title="group.fullName"
- class="no-expand"
- data-placement="bottom"
- >{{
- // ending bracket must be by closing tag to prevent
- // link hover text-decoration from over-extending
- group.name
- }}</a
- >
- <span v-if="group.permission" class="user-access-role"> {{ group.permission }} </span>
+ <div class="group-text-container d-flex flex-fill align-items-center">
+ <div class="group-text flex-grow-1 flex-shrink-1">
+ <div class="d-flex align-items-center flex-wrap title namespace-title append-right-8">
+ <a
+ v-tooltip
+ :href="group.relativePath"
+ :title="group.fullName"
+ class="no-expand prepend-top-8 append-right-8"
+ data-placement="bottom"
+ >{{
+ // ending bracket must be by closing tag to prevent
+ // link hover text-decoration from over-extending
+ group.name
+ }}</a
+ >
+ <item-stats-value
+ :icon-name="visibilityIcon"
+ :title="visibilityTooltip"
+ css-class="item-visibility d-inline-flex align-items-center prepend-top-8 append-right-4"
+ />
+ <span v-if="group.permission" class="user-access-role prepend-top-8">
+ {{ group.permission }}
+ </span>
+ </div>
+ <div v-if="group.description" class="description">
+ <span v-html="group.description"> </span>
+ </div>
</div>
- <div v-if="group.description" class="description">
- <span v-html="group.description"> </span>
+ <div
+ class="metadata align-items-md-center d-flex flex-grow-1 flex-shrink-0 flex-wrap justify-content-md-between"
+ >
+ <item-actions v-if="isGroup" :group="group" :parent-group="parentGroup" />
+ <item-stats :item="group" class="group-stats prepend-top-2 d-none d-md-flex" />
</div>
</div>
- <item-stats :item="group" class="group-stats prepend-top-2" />
- <item-actions v-if="isGroup" :group="group" :parent-group="parentGroup" />
</div>
<group-folder
v-if="group.isOpen && hasChildren"
diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue
index a7995865c77..cafd22731b1 100644
--- a/app/assets/javascripts/groups/components/item_actions.vue
+++ b/app/assets/javascripts/groups/components/item_actions.vue
@@ -44,31 +44,31 @@ export default {
</script>
<template>
- <div class="controls">
+ <div class="controls d-flex justify-content-end">
<a
- v-if="group.canEdit"
+ v-if="group.canLeave"
v-tooltip
- :href="group.editPath"
- :title="editBtnTitle"
- :aria-label="editBtnTitle"
+ :href="group.leavePath"
+ :title="leaveBtnTitle"
+ :aria-label="leaveBtnTitle"
data-container="body"
data-placement="bottom"
- class="edit-group btn no-expand"
+ class="leave-group btn btn-xs no-expand"
+ @click.prevent="onLeaveGroup"
>
- <icon name="settings" />
+ <icon name="leave" css-classes="position-top-0" />
</a>
<a
- v-if="group.canLeave"
+ v-if="group.canEdit"
v-tooltip
- :href="group.leavePath"
- :title="leaveBtnTitle"
- :aria-label="leaveBtnTitle"
+ :href="group.editPath"
+ :title="editBtnTitle"
+ :aria-label="editBtnTitle"
data-container="body"
data-placement="bottom"
- class="leave-group btn no-expand"
- @click.prevent="onLeaveGroup"
+ class="edit-group btn btn-xs no-expand"
>
- <icon name="leave" />
+ <icon name="settings" css-classes="position-top-0" />
</a>
</div>
</template>
diff --git a/app/assets/javascripts/groups/components/item_caret.vue b/app/assets/javascripts/groups/components/item_caret.vue
index 43b9607ea8e..18ea4819878 100644
--- a/app/assets/javascripts/groups/components/item_caret.vue
+++ b/app/assets/javascripts/groups/components/item_caret.vue
@@ -21,5 +21,5 @@ export default {
</script>
<template>
- <span class="folder-caret"> <icon :size="12" :name="iconClass" /> </span>
+ <span class="folder-caret append-right-4"> <icon :size="10" :name="iconClass" /> </span>
</template>
diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue
index bc6851ea2bf..734a9a89c72 100644
--- a/app/assets/javascripts/groups/components/item_stats.vue
+++ b/app/assets/javascripts/groups/components/item_stats.vue
@@ -48,7 +48,7 @@ export default {
:title="__('Subgroups')"
:value="item.subgroupCount"
css-class="number-subgroups"
- icon-name="folder"
+ icon-name="folder-o"
/>
<item-stats-value
v-if="isGroup"
@@ -70,12 +70,6 @@ export default {
css-class="project-stars"
icon-name="star"
/>
- <item-stats-value
- :icon-name="visibilityIcon"
- :title="visibilityTooltip"
- css-class="item-visibility"
- tooltip-placement="left"
- />
<div v-if="isProject" class="last-updated">
<time-ago-tooltip :time="item.updatedAt" tooltip-placement="bottom" />
</div>
diff --git a/app/assets/javascripts/groups/components/item_type_icon.vue b/app/assets/javascripts/groups/components/item_type_icon.vue
index e1ebd03cb5f..ae69fbd7bde 100644
--- a/app/assets/javascripts/groups/components/item_type_icon.vue
+++ b/app/assets/javascripts/groups/components/item_type_icon.vue
@@ -20,7 +20,7 @@ export default {
computed: {
iconClass() {
if (this.itemType === ITEM_TYPE.GROUP) {
- return this.isGroupOpen ? 'folder-open' : 'folder';
+ return this.isGroupOpen ? 'folder-open' : 'folder-o';
}
return 'bookmark';
},
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 607b2bd1c74..156735441ca 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -3,7 +3,7 @@ import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { polyfillSticky } from '~/lib/utils/sticky';
import Icon from '~/vue_shared/components/icon.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
-import { sprintf } from '~/locale';
+import { __, sprintf } from '~/locale';
import scrollDown from '../svg/scroll_down.svg';
export default {
@@ -50,7 +50,7 @@ export default {
},
computed: {
jobLogSize() {
- return sprintf('Showing last %{size} of log -', {
+ return sprintf(__('Showing last %{size} of log -'), {
size: numberToHumanSize(this.size),
});
},
@@ -74,14 +74,12 @@ export default {
<div class="js-truncated-info truncated-info d-none d-sm-block float-left">
<template v-if="isTraceSizeVisible">
{{ jobLogSize }}
-
<gl-link
v-if="rawPath"
:href="rawPath"
class="js-raw-link text-plain text-underline prepend-left-5"
+ >{{ s__('Job|Complete Raw') }}</gl-link
>
- {{ s__('Job|Complete Raw') }}
- </gl-link>
</template>
</div>
<!-- eo truncate information -->
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 24276c06486..e9704584c9f 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -1,4 +1,5 @@
<script>
+import { __, sprintf } from '~/locale';
import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import { GlLink, GlButton } from '@gitlab/ui';
@@ -63,7 +64,9 @@ export default {
let t = this.job.metadata.timeout_human_readable;
if (this.job.metadata.timeout_source !== '') {
- t += ` (from ${this.job.metadata.timeout_source})`;
+ t += sprintf(__(` (from %{timeoutSource})`), {
+ timeoutSource: this.job.metadata.timeout_source,
+ });
}
return t;
diff --git a/app/assets/javascripts/monitoring/components/charts/column.vue b/app/assets/javascripts/monitoring/components/charts/column.vue
new file mode 100644
index 00000000000..05a2036f4c3
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/column.vue
@@ -0,0 +1,131 @@
+<script>
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
+import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
+import { chartHeight } from '../../constants';
+import { makeDataSeries } from '~/helpers/monitor_helper';
+
+export default {
+ components: {
+ GlColumnChart,
+ },
+ inheritAttrs: false,
+ props: {
+ graphData: {
+ type: Object,
+ required: true,
+ validator(data) {
+ return (
+ Array.isArray(data.queries) &&
+ data.queries.filter(query => {
+ if (Array.isArray(query.result)) {
+ return (
+ query.result.filter(res => Array.isArray(res.values)).length === query.result.length
+ );
+ }
+ return false;
+ }).length === data.queries.length
+ );
+ },
+ containerWidth: {
+ type: Number,
+ required: true,
+ },
+ },
+ },
+ data() {
+ return {
+ width: 0,
+ height: chartHeight,
+ svgs: {},
+ debouncedResizeCallback: {},
+ };
+ },
+ computed: {
+ chartData() {
+ const queryData = this.graphData.queries.reduce((acc, query) => {
+ const series = makeDataSeries(query.result, {
+ name: this.formatLegendLabel(query),
+ });
+
+ return acc.concat(series);
+ }, []);
+
+ return {
+ values: queryData[0].data,
+ };
+ },
+ xAxisTitle() {
+ return this.graphData.queries[0].result[0].x_label !== undefined
+ ? this.graphData.queries[0].result[0].x_label
+ : '';
+ },
+ yAxisTitle() {
+ return this.graphData.queries[0].result[0].y_label !== undefined
+ ? this.graphData.queries[0].result[0].y_label
+ : '';
+ },
+ xAxisType() {
+ return this.graphData.x_type !== undefined ? this.graphData.x_type : 'category';
+ },
+ dataZoomConfig() {
+ const handleIcon = this.svgs['scroll-handle'];
+
+ return handleIcon ? { handleIcon } : {};
+ },
+ chartOptions() {
+ return {
+ dataZoom: this.dataZoomConfig,
+ };
+ },
+ },
+ watch: {
+ containerWidth: 'onResize',
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', this.debouncedResizeCallback);
+ },
+ created() {
+ this.debouncedResizeCallback = debounceByAnimationFrame(this.onResize);
+ window.addEventListener('resize', this.debouncedResizeCallback);
+ this.setSvg('scroll-handle');
+ },
+ methods: {
+ formatLegendLabel(query) {
+ return `${query.label}`;
+ },
+ onResize() {
+ const { width } = this.$refs.columnChart.$el.getBoundingClientRect();
+ this.width = width;
+ },
+ setSvg(name) {
+ getSvgIconPathContent(name)
+ .then(path => {
+ if (path) {
+ this.$set(this.svgs, name, `path://${path}`);
+ }
+ })
+ .catch(() => {});
+ },
+ },
+};
+</script>
+<template>
+ <div class="prometheus-graph col-12 col-lg-6">
+ <div class="prometheus-graph-header">
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
+ <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
+ </div>
+ <gl-column-chart
+ ref="columnChart"
+ v-bind="$attrs"
+ :data="chartData"
+ :option="chartOptions"
+ :width="width"
+ :height="height"
+ :x-axis-title="xAxisTitle"
+ :y-axis-title="yAxisTitle"
+ :x-axis-type="xAxisType"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index eb6a4a67fff..10b15a9c38c 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -382,7 +382,7 @@ Please check your network connection and try again.`;
class="discussion-reply-holder"
>
<user-avatar-link
- v-if="!isReplying && currentUser"
+ v-if="!isReplying && userCanReply"
:link-href="currentUser.path"
:img-src="currentUser.avatar_url"
:img-alt="currentUser.name"
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index 8fa266a37ce..29de3b7806c 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -1,7 +1,9 @@
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
import DeleteModal from '~/branches/branches_delete_modal';
+import initDiverganceGraph from '~/branches/divergence_graph';
document.addEventListener('DOMContentLoaded', () => {
AjaxLoadingSpinner.init();
new DeleteModal(); // eslint-disable-line no-new
+ initDiverganceGraph();
});
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index c41ecab1294..65a2b61396c 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -60,7 +60,7 @@ export default {
</script>
<template>
<div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags">
- <gl-link :href="pipeline.path" class="js-pipeline-url-link">
+ <gl-link :href="pipeline.path" class="js-pipeline-url-link js-onboarding-pipeline-item">
<span class="pipeline-id">#{{ pipeline.id }}</span>
</gl-link>
<div class="label-container">
diff --git a/app/assets/javascripts/vue_shared/components/pagination/constants.js b/app/assets/javascripts/vue_shared/components/pagination/constants.js
index 748ad178c70..229132c0e33 100644
--- a/app/assets/javascripts/vue_shared/components/pagination/constants.js
+++ b/app/assets/javascripts/vue_shared/components/pagination/constants.js
@@ -3,8 +3,8 @@ import { s__ } from '~/locale';
export const PAGINATION_UI_BUTTON_LIMIT = 4;
export const UI_LIMIT = 6;
export const SPREAD = '...';
-export const PREV = s__('Pagination|Prev');
-export const NEXT = s__('Pagination|Next');
+export const PREV = s__('Pagination|‹ Prev');
+export const NEXT = s__('Pagination|Next ›');
export const FIRST = s__('Pagination|« First');
export const LAST = s__('Pagination|Last »');
export const LABEL_FIRST_PAGE = s__('Pagination|Go to first page');
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
index e77b9ddc7ba..b9311d65360 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
@@ -17,13 +17,9 @@
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '../../../lazy_loader';
-import tooltip from '../../directives/tooltip';
export default {
name: 'ProjectAvatarImage',
- directives: {
- tooltip,
- },
props: {
lazy: {
type: Boolean,
@@ -50,16 +46,6 @@ export default {
required: false,
default: 20,
},
- tooltipText: {
- type: String,
- required: false,
- default: '',
- },
- tooltipPlacement: {
- type: String,
- required: false,
- default: 'top',
- },
},
computed: {
// API response sends null when gravatar is disabled and
@@ -71,9 +57,6 @@ export default {
resultantSrcAttribute() {
return this.lazy ? placeholderImage : this.sanitizedSource;
},
- tooltipContainer() {
- return this.tooltipText ? 'body' : null;
- },
avatarSizeClass() {
return `s${this.size}`;
},
@@ -83,7 +66,6 @@ export default {
<template>
<img
- v-tooltip
:class="{
lazy: lazy,
[avatarSizeClass]: true,
@@ -94,9 +76,6 @@ export default {
:height="size"
:alt="imgAlt"
:data-src="sanitizedSource"
- :data-container="tooltipContainer"
- :data-placement="tooltipPlacement"
- :title="tooltipText"
class="avatar"
/>
</template>
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index 58aaca93160..8c40c4adb5c 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -135,11 +135,5 @@
.popover {
min-width: auto;
max-width: 40%;
-
- .popover-body {
- padding-top: $gl-padding;
- padding-bottom: $gl-padding;
- font-size: $gl-font-size-small;
- }
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 555a3fe0dc7..954551fef97 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -133,7 +133,6 @@ ul.content-list {
.description {
@include str-truncated;
- color: $gl-text-color-secondary;
}
.controls {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index ad5096761cd..bf0f1da6aa3 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -65,7 +65,7 @@
.stats {
float: right;
line-height: $list-text-height;
- color: $gl-text-color;
+ color: $gl-text-color-secondary;
span {
margin-right: 15px;
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
index ce0622b3d48..e1715b8e1bf 100644
--- a/app/assets/stylesheets/pages/branches.scss
+++ b/app/assets/stylesheets/pages/branches.scss
@@ -14,62 +14,26 @@
$graph-side-width: 80px;
$graph-separator-width: 1px;
- padding: 0 6px;
-
.graph-side {
- position: relative;
width: $graph-side-width;
- height: 22px;
- padding: 5px 0 13px;
- float: left;
&.full {
width: $graph-side-width * 2 + $graph-separator-width;
- display: flex;
- justify-content: center;
}
.bar {
- position: absolute;
height: 4px;
background-color: $gl-gray-200;
}
- .bar-behind {
- right: 0;
- border-radius: 3px 0 0 3px;
- }
-
- .bar-ahead {
- left: 0;
- border-radius: 0 3px 3px 0;
- }
-
.count {
- padding-top: 6px;
- padding-bottom: 0;
font-size: 12px;
- color: $gl-text-color;
- display: block;
- }
-
- .count-behind {
- padding-right: 4px;
- text-align: right;
- }
-
- .count-ahead {
- padding-left: 4px;
- text-align: left;
}
}
.graph-separator {
- position: relative;
width: $graph-separator-width;
height: 18px;
- margin: 5px 0 0;
- float: left;
background-color: $gl-gray-200;
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 656202f4e58..cff2e274390 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -168,12 +168,6 @@
}
}
-.groups-listing {
- .group-list-tree .group-row:first-child {
- border-top: 0;
- }
-}
-
.card {
.shared_runners_limit_under_quota {
color: $green-500;
@@ -260,7 +254,6 @@ table.pipeline-project-metrics tr td {
color: $gl-text-color-secondary;
font-size: 12px;
line-height: 20px;
- margin: -5px 3px;
padding: 0 $label-padding;
border: 1px solid $border-color;
border-radius: $label-border-radius;
@@ -294,39 +287,6 @@ table.pipeline-project-metrics tr td {
}
.group-list-tree {
- .avatar-container.content-loading {
- position: relative;
-
- > a,
- > a .avatar {
- height: 100%;
- border-radius: 50%;
- }
-
- > a {
- padding: 2px;
-
- .avatar {
- border: 2px solid $white-normal;
-
- &.identicon {
- line-height: 15px;
- }
- }
- }
-
- &::after {
- content: '';
- position: absolute;
- height: 100%;
- width: 100%;
- background-color: transparent;
- border: 2px outset $gl-gray-200;
- border-radius: 50%;
- animation: spin-avatar 3s infinite linear;
- }
- }
-
.folder-toggle-wrap {
font-size: 0;
flex-shrink: 0;
@@ -339,13 +299,14 @@ table.pipeline-project-metrics tr td {
.folder-caret,
.item-type-icon {
display: inline-block;
+ color: $gl-text-color-secondary;
}
.folder-caret {
- width: 15px;
+ width: $gl-font-size-large;
svg {
- margin-bottom: 1px;
+ margin-bottom: 2px;
}
}
@@ -420,7 +381,7 @@ table.pipeline-project-metrics tr td {
}
.group-row-contents {
- padding: $gl-padding-top;
+ padding: $gl-padding;
&:hover {
border-color: $blue-200;
@@ -428,10 +389,15 @@ table.pipeline-project-metrics tr td {
cursor: pointer;
}
+ .group-text-container,
.group-text {
min-width: 0; // allows for truncated text within flex children
}
+ .group-text {
+ flex-basis: 100%;
+ }
+
.avatar-container {
flex-shrink: 0;
@@ -441,6 +407,21 @@ table.pipeline-project-metrics tr td {
}
}
+ .title {
+ margin-top: -$gl-padding-8; // negative margin required for flex-wrap
+ font-size: $gl-font-size-large;
+ }
+
+ .item-visibility {
+ color: $gl-text-color-secondary;
+ }
+
+ @include media-breakpoint-down(md) {
+ .title {
+ font-size: $gl-font-size;
+ }
+ }
+
&.has-more-items {
display: block;
padding: 20px 10px;
@@ -477,17 +458,18 @@ table.pipeline-project-metrics tr td {
}
.controls {
- flex-shrink: 0;
+ flex-basis: 90px;
> .btn {
- margin: 0 0 0 $btn-margin-5;
+ margin: 0 $btn-side-margin 0 0;
+ color: $gl-text-color-secondary;
}
}
- }
- @include media-breakpoint-down(xs) {
- .group-stats {
- display: none;
+ .metadata {
+ @include media-breakpoint-up(md) {
+ flex-basis: 240px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 151af843c95..c80beceae52 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -889,7 +889,6 @@ pre.light-well {
@include basic-list-stats;
display: flex;
align-items: center;
- color: $gl-text-color-secondary;
padding: $gl-padding 0;
@include media-breakpoint-up(lg) {
@@ -952,10 +951,6 @@ pre.light-well {
.description {
line-height: 1.5;
-
- @include media-breakpoint-up(md) {
- color: $gl-text-color;
- }
}
@include media-breakpoint-down(md) {
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index f173c263474..27980466a42 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -10,6 +10,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def index
@sort = params[:sort]
@todos = @todos.page(params[:page])
+ @todos = @todos.with_entity_associations
return if redirect_out_of_range(@todos)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9e7e3ed5afb..fc37ce1dbc4 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -16,7 +16,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
- around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
+ around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
def index
@merge_requests = @issuables
@@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def show
close_merge_request_if_no_source_project
- mark_merge_request_mergeable
+ @merge_request.check_mergeability
respond_to do |format|
format.html do
@@ -251,10 +251,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.has_no_commits? && !@merge_request.target_branch_exists?
end
- def mark_merge_request_mergeable
- @merge_request.check_if_can_be_merged
- end
-
def merge!
# Disable the CI check if auto_merge_strategy is specified since we have
# to wait until CI completes to know
diff --git a/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb
index f38c187799c..78a17312e26 100644
--- a/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb
+++ b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb
@@ -22,8 +22,7 @@ module Autocomplete
end
def filter_by_name(tags)
- return tags unless search
- return tags.none if search.empty?
+ return tags unless search.present?
if search.length >= Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING
tags.named_like(search)
diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb
index 419be46fafe..29c00e4b2c2 100644
--- a/app/finders/environments_finder.rb
+++ b/app/finders/environments_finder.rb
@@ -47,6 +47,19 @@ class EnvironmentsFinder
end
# rubocop: enable CodeReuse/ActiveRecord
+ # This method will eventually take the place of `#execute` as an
+ # efficient way to get relevant environment entries.
+ # Currently, `#execute` method has a serious technical debt and
+ # we will likely rework on it in the future.
+ # See more https://gitlab.com/gitlab-org/gitlab-ce/issues/63381
+ def find
+ environments = project.environments
+ environments = by_name(environments)
+ environments = by_search(environments)
+
+ environments
+ end
+
private
def ref
@@ -56,4 +69,20 @@ class EnvironmentsFinder
def commit
params[:commit]
end
+
+ def by_name(environments)
+ if params[:name].present?
+ environments.for_name(params[:name])
+ else
+ environments
+ end
+ end
+
+ def by_search(environments)
+ if params[:search].present?
+ environments.for_name_like(params[:search], limit: nil)
+ else
+ environments
+ end
+ end
end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index c0db9910143..6b43d52c775 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -2,6 +2,7 @@
module AppearancesHelper
include MarkupHelper
+ include Gitlab::Utils::StrongMemoize
def brand_title
current_appearance&.title.presence || default_brand_title
@@ -25,7 +26,9 @@ module AppearancesHelper
end
def current_appearance
- @appearance ||= Appearance.current
+ strong_memoize(:current_appearance) do
+ Appearance.current
+ end
end
def brand_header_logo
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 957ab06b0ca..59332c0b100 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -135,6 +135,20 @@ module IssuesHelper
can?(current_user, :create_issue, project)
end
+ def create_confidential_merge_request_enabled?
+ Feature.enabled?(:create_confidential_merge_request, @project)
+ end
+
+ def show_new_branch_button?
+ can_create_confidential_merge_request? || !@issue.confidential?
+ end
+
+ def can_create_confidential_merge_request?
+ @issue.confidential? && !@project.private? &&
+ create_confidential_merge_request_enabled? &&
+ can?(current_user, :create_merge_request_in, @project)
+ end
+
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
module_function :url_for_internal_issue
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 6bd78336ed3..645160077f5 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -170,7 +170,7 @@ module TodosHelper
end
def todo_group_options
- groups = current_user.authorized_groups.map do |group|
+ groups = current_user.authorized_groups.with_route.map do |group|
{ id: group.id, text: group.full_name }
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 70d296fe3b8..506c8d251b7 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -60,7 +60,7 @@ module Emails
# `note_id` is a `Note` when originating in `NotifyPreview`
@note = note_id.is_a?(Note) ? note_id : Note.find(note_id)
@project = @note.project
- @group = @note.noteable.try(:group)
+ @group = @project.try(:group) || @note.noteable.try(:group)
if (@project || @group) && @note.persisted?
@sent_notification = SentNotification.record_note(@note, recipient_id, reply_key)
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 6a4241c94bc..ba8cea0cea9 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -55,15 +55,20 @@ module Ci
# This way, a schedule like `*/1 * * * *` won't be triggered in a short interval
# when PipelineScheduleWorker runs irregularly by Sidekiq Memory Killer.
def set_next_run_at
- self.next_run_at = Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'],
- Time.zone.name)
- .next_time_from(ideal_next_run_at)
+ now = Time.zone.now
+ ideal_next_run = ideal_next_run_from(now)
+
+ self.next_run_at = if ideal_next_run == cron_worker_next_run_from(now)
+ ideal_next_run
+ else
+ cron_worker_next_run_from(ideal_next_run)
+ end
end
def schedule_next_run!
save! # with set_next_run_at
rescue ActiveRecord::RecordInvalid
- update_attribute(:next_run_at, nil) # update without validation
+ update_column(:next_run_at, nil) # update without validation
end
def job_variables
@@ -72,9 +77,15 @@ module Ci
private
- def ideal_next_run_at
+ def ideal_next_run_from(start_time)
Gitlab::Ci::CronParser.new(cron, cron_timezone)
- .next_time_from(Time.zone.now)
+ .next_time_from(start_time)
+ end
+
+ def cron_worker_next_run_from(start_time)
+ Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'],
+ Time.zone.name)
+ .next_time_from(start_time)
end
end
end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 2afe471d1cc..5afb193cf86 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -47,7 +47,6 @@ module Clusters
validate :prevent_modification, on: :update
after_save :clear_reactive_cache!
- after_update :update_kubernetes_namespace
alias_attribute :ca_pem, :ca_cert
@@ -210,14 +209,6 @@ module Clusters
true
end
-
- def update_kubernetes_namespace
- return unless saved_change_to_namespace?
-
- run_after_commit do
- ClusterConfigureWorker.perform_async(cluster_id)
- end
- end
end
end
end
diff --git a/app/models/concerns/service_push_data_validations.rb b/app/models/concerns/service_push_data_validations.rb
new file mode 100644
index 00000000000..defc5794142
--- /dev/null
+++ b/app/models/concerns/service_push_data_validations.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# This concern is used by registerd services such as TeamCityService and
+# DroneCiService and add methods to perform validations on the received
+# data.
+
+module ServicePushDataValidations
+ extend ActiveSupport::Concern
+
+ def merge_request_valid?(data)
+ data.dig(:object_attributes, :state) == 'opened' && merge_request_unchecked?(data)
+ end
+
+ def push_valid?(data)
+ data[:total_commits_count] > 0 &&
+ !branch_removed?(data) &&
+ # prefer merge request trigger over push to avoid double builds
+ !opened_merge_requests?(data)
+ end
+
+ def tag_push_valid?(data)
+ data[:total_commits_count] > 0 && !branch_removed?(data)
+ end
+
+ private
+
+ def branch_removed?(data)
+ Gitlab::Git.blank_ref?(data[:after])
+ end
+
+ def opened_merge_requests?(data)
+ project.merge_requests
+ .opened
+ .from_project(project)
+ .from_source_branches(Gitlab::Git.ref_name(data[:ref]))
+ .exists?
+ end
+
+ def merge_request_unchecked?(data)
+ MergeRequest.state_machines[:merge_status]
+ .check_state?(data.dig(:object_attributes, :merge_status))
+ end
+end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index e0648746e31..f0fa5974787 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -176,7 +176,7 @@ class Deployment < ApplicationRecord
end
def has_metrics?
- prometheus_adapter&.can_query? && success?
+ success? && prometheus_adapter&.can_query?
end
def metrics
diff --git a/app/models/environment.rb b/app/models/environment.rb
index aff20dae09b..1f7e8815c8e 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -155,7 +155,7 @@ class Environment < ApplicationRecord
end
def has_terminals?
- deployment_platform.present? && available? && last_deployment.present?
+ available? && deployment_platform.present? && last_deployment.present?
end
def terminals
@@ -163,7 +163,7 @@ class Environment < ApplicationRecord
end
def has_metrics?
- prometheus_adapter&.can_query? && available?
+ available? && prometheus_adapter&.can_query?
end
def metrics
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 6da6fbe55cb..30e29911758 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -254,6 +254,10 @@ class Issue < ApplicationRecord
merge_requests_closing_issues.count
end
+ def labels_hook_attrs
+ labels.map(&:hook_attrs)
+ end
+
private
def ensure_metrics
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f07636e8f77..df2dc9c49eb 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -725,19 +725,16 @@ class MergeRequest < ApplicationRecord
MergeRequests::ReloadDiffsService.new(self, current_user).execute
end
- # rubocop: enable CodeReuse/ServiceClass
-
- def check_if_can_be_merged
- return unless self.class.state_machines[:merge_status].check_state?(merge_status) && Gitlab::Database.read_write?
- can_be_merged =
- !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
+ def check_mergeability
+ MergeRequests::MergeabilityCheckService.new(self).execute
+ end
+ # rubocop: enable CodeReuse/ServiceClass
- if can_be_merged
- mark_as_mergeable
- else
- mark_as_unmergeable
- end
+ # Returns boolean indicating the merge_status should be rechecked in order to
+ # switch to either can_be_merged or cannot_be_merged.
+ def recheck_merge_status?
+ self.class.state_machines[:merge_status].check_state?(merge_status)
end
def merge_event
@@ -763,7 +760,7 @@ class MergeRequest < ApplicationRecord
def mergeable?(skip_ci_check: false)
return false unless mergeable_state?(skip_ci_check: skip_ci_check)
- check_if_can_be_merged
+ check_mergeability
can_be_merged? && !should_be_rebased?
end
@@ -778,15 +775,6 @@ class MergeRequest < ApplicationRecord
true
end
- def mergeable_to_ref?
- return false unless mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
-
- # Given the `merge_ref_path` will have the same
- # state the `target_branch` would have. Ideally
- # we need to check if it can be merged to it.
- project.repository.can_be_merged?(diff_head_sha, target_branch)
- end
-
def ff_merge_possible?
project.repository.ancestor?(target_branch_sha, diff_head_sha)
end
@@ -1099,6 +1087,12 @@ class MergeRequest < ApplicationRecord
target_project.repository.fetch_source_branch!(source_project.repository, source_branch, ref_path)
end
+ # Returns the current merge-ref HEAD commit.
+ #
+ def merge_ref_head
+ project.repository.commit(merge_ref_path)
+ end
+
def ref_path
"refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head"
end
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 07195c0bfd3..d6d879c6d89 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -3,6 +3,7 @@
class PagesDomain < ApplicationRecord
VERIFICATION_KEY = 'gitlab-pages-verification-code'.freeze
VERIFICATION_THRESHOLD = 3.days.freeze
+ SSL_RENEWAL_THRESHOLD = 30.days.freeze
enum certificate_source: { user_provided: 0, gitlab_provided: 1 }, _prefix: :certificate
@@ -41,6 +42,15 @@ class PagesDomain < ApplicationRecord
where(verified_at.eq(nil).or(enabled_until.eq(nil).or(enabled_until.lt(threshold))))
end
+ scope :need_auto_ssl_renewal, -> do
+ expiring = where(certificate_valid_not_after: nil).or(
+ where(arel_table[:certificate_valid_not_after].lt(SSL_RENEWAL_THRESHOLD.from_now)))
+
+ user_provided_or_expiring = certificate_user_provided.or(expiring)
+
+ where(auto_ssl_enabled: true).merge(user_provided_or_expiring)
+ end
+
scope :for_removal, -> { where("remove_at < ?", Time.now) }
def verified?
diff --git a/app/models/project.rb b/app/models/project.rb
index 7851f37116c..351d08eaf63 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -55,6 +55,8 @@ class Project < ApplicationRecord
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
+ ignore_column :import_status, :import_jid, :import_error
+
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 5ccc2f019cb..dbdc8345c93 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -2,6 +2,7 @@
class DroneCiService < CiService
include ReactiveService
+ include ServicePushDataValidations
prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification
@@ -96,23 +97,4 @@ class DroneCiService < CiService
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
]
end
-
- private
-
- def tag_push_valid?(data)
- data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after])
- end
-
- def push_valid?(data)
- opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
- source_branch: Gitlab::Git.ref_name(data[:ref]))
-
- opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
- !Gitlab::Git.blank_ref?(data[:after])
- end
-
- def merge_request_valid?(data)
- data[:object_attributes][:state] == 'opened' &&
- MergeRequest.state_machines[:merge_status].check_state?(data[:object_attributes][:merge_status])
- end
end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 3245cd22e73..68c07fa37f2 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -2,6 +2,7 @@
class TeamcityService < CiService
include ReactiveService
+ include ServicePushDataValidations
prop_accessor :teamcity_url, :build_type, :username, :password
@@ -19,6 +20,25 @@ class TeamcityService < CiService
after_save :compose_service_hook, if: :activated?
before_update :reset_password
+ class << self
+ def to_param
+ 'teamcity'
+ end
+
+ def supported_events
+ %w(push merge_request)
+ end
+
+ def event_description(event)
+ case event
+ when 'push', 'push_events'
+ 'TeamCity CI will be triggered after every push to the repository except branch delete'
+ when 'merge_request', 'merge_request_events'
+ 'TeamCity CI will be triggered after a merge request has been created or updated'
+ end
+ end
+ end
+
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
@@ -43,10 +63,6 @@ class TeamcityService < CiService
'requests build, that setting is in the vsc root advanced settings.'
end
- def self.to_param
- 'teamcity'
- end
-
def fields
[
{ type: 'text', name: 'teamcity_url',
@@ -74,26 +90,25 @@ class TeamcityService < CiService
end
def execute(data)
- return unless supported_events.include?(data[:object_kind])
+ case data[:object_kind]
+ when 'push'
+ execute_push(data)
+ when 'merge_request'
+ execute_merge_request(data)
+ end
+ end
- auth = {
- username: username,
- password: password
- }
+ private
+ def execute_push(data)
branch = Gitlab::Git.ref_name(data[:ref])
-
- Gitlab::HTTP.post(
- build_url('httpAuth/app/rest/buildQueue'),
- body: "<build branchName=\"#{branch}\">"\
- "<buildType id=\"#{build_type}\"/>"\
- '</build>',
- headers: { 'Content-type' => 'application/xml' },
- basic_auth: auth
- )
+ post_to_build_queue(data, branch) if push_valid?(data)
end
- private
+ def execute_merge_request(data)
+ branch = data[:object_attributes][:source_branch]
+ post_to_build_queue(data, branch) if merge_request_valid?(data)
+ end
def read_build_page(response)
if response.code != 200
@@ -134,10 +149,21 @@ class TeamcityService < CiService
end
def get_path(path)
- Gitlab::HTTP.get(build_url(path), verify: false,
- basic_auth: {
- username: username,
- password: password
- })
+ Gitlab::HTTP.get(build_url(path), verify: false, basic_auth: basic_auth)
+ end
+
+ def post_to_build_queue(data, branch)
+ Gitlab::HTTP.post(
+ build_url('httpAuth/app/rest/buildQueue'),
+ body: "<build branchName=#{branch.encode(xml: :attr)}>"\
+ "<buildType id=#{build_type.encode(xml: :attr)}/>"\
+ '</build>',
+ headers: { 'Content-type' => 'application/xml' },
+ basic_auth: basic_auth
+ )
+ end
+
+ def basic_auth
+ { username: username, password: password }
end
end
diff --git a/app/models/todo.rb b/app/models/todo.rb
index f1fc5e599eb..240c91da5b6 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -60,7 +60,7 @@ class Todo < ApplicationRecord
scope :for_type, -> (type) { where(target_type: type) }
scope :for_target, -> (id) { where(target_id: id) }
scope :for_commit, -> (id) { where(commit_id: id) }
- scope :with_api_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) }
+ scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) }
scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
state_machine :state, initial: :pending do
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 5525c1b9b7f..2f3c1df7651 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -12,9 +12,6 @@ module Clusters
create_gitlab_service_account!
configure_kubernetes
cluster.save!
-
- ClusterConfigureWorker.perform_async(cluster.id)
-
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
log_service_error(e.class.name, provider.id, e.message)
provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message })
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
index 87147d90c32..efe4dcd6255 100644
--- a/app/services/merge_requests/merge_to_ref_service.rb
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -11,6 +11,8 @@ module MergeRequests
# be executed regardless of the `target_ref` current state).
#
class MergeToRefService < MergeRequests::MergeBaseService
+ extend ::Gitlab::Utils::Override
+
def execute(merge_request)
@merge_request = merge_request
@@ -26,14 +28,18 @@ module MergeRequests
success(commit_id: commit.id,
target_id: target_id,
source_id: source_id)
- rescue MergeError => error
+ rescue MergeError, ArgumentError => error
error(error.message)
end
private
+ override :source
+ def source
+ merge_request.diff_head_sha
+ end
+
def validate!
- authorization_check!
error_check!
end
@@ -43,21 +49,13 @@ module MergeRequests
error =
if !hooks_validation_pass?(merge_request)
hooks_validation_error(merge_request)
- elsif !@merge_request.mergeable_to_ref?
- "Merge request is not mergeable to #{target_ref}"
- elsif !source
+ elsif source.blank?
'No source for merge'
end
raise_error(error) if error
end
- def authorization_check!
- unless Ability.allowed?(current_user, :admin_merge_request, project)
- raise_error("You are not allowed to merge to this ref")
- end
- end
-
def target_ref
merge_request.merge_ref_path
end
diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb
new file mode 100644
index 00000000000..9fa50c9448f
--- /dev/null
+++ b/app/services/merge_requests/mergeability_check_service.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeabilityCheckService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :project, to: :@merge_request
+ delegate :repository, to: :project
+
+ def initialize(merge_request)
+ @merge_request = merge_request
+ end
+
+ # Updates the MR merge_status. Whenever it switches to a can_be_merged state,
+ # the merge-ref is refreshed.
+ #
+ # recheck - When given, it'll enforce a merge-ref refresh if the current merge_status is
+ # can_be_merged or cannot_be_merged and merge-ref is outdated.
+ # Given MergeRequests::RefreshService is called async, it might happen that the target
+ # branch gets updated, but the MergeRequest#merge_status lags behind. So in scenarios
+ # where we need the current state of the merge ref in repository, the `recheck`
+ # argument is required.
+ #
+ # Returns a ServiceResponse indicating merge_status is/became can_be_merged
+ # and the merge-ref is synced. Success in case of being/becoming mergeable,
+ # error otherwise.
+ def execute(recheck: false)
+ return ServiceResponse.error(message: 'Invalid argument') unless merge_request
+ return ServiceResponse.error(message: 'Unsupported operation') if Gitlab::Database.read_only?
+
+ recheck! if recheck
+ update_merge_status
+
+ unless merge_request.can_be_merged?
+ return ServiceResponse.error(message: 'Merge request is not mergeable')
+ end
+
+ unless merge_ref_auto_sync_enabled?
+ return ServiceResponse.error(message: 'Merge ref is outdated due to disabled feature')
+ end
+
+ unless payload.fetch(:merge_ref_head)
+ return ServiceResponse.error(message: 'Merge ref cannot be updated')
+ end
+
+ ServiceResponse.success(payload: payload)
+ end
+
+ private
+
+ attr_reader :merge_request
+
+ def payload
+ strong_memoize(:payload) do
+ {
+ merge_ref_head: merge_ref_head_payload
+ }
+ end
+ end
+
+ def merge_ref_head_payload
+ commit = merge_request.merge_ref_head
+
+ return unless commit
+
+ target_id, source_id = commit.parent_ids
+
+ {
+ commit_id: commit.id,
+ source_id: source_id,
+ target_id: target_id
+ }
+ end
+
+ def update_merge_status
+ return unless merge_request.recheck_merge_status?
+
+ if can_git_merge? && merge_to_ref
+ merge_request.mark_as_mergeable
+ else
+ merge_request.mark_as_unmergeable
+ end
+ end
+
+ def recheck!
+ if !merge_request.recheck_merge_status? && outdated_merge_ref?
+ merge_request.mark_as_unchecked
+ end
+ end
+
+ # Checks if the existing merge-ref is synced with the target branch.
+ #
+ # Returns true if the merge-ref does not exists or is out of sync.
+ def outdated_merge_ref?
+ return false unless merge_ref_auto_sync_enabled?
+ return false unless merge_request.open?
+
+ return true unless ref_head = merge_request.merge_ref_head
+ return true unless target_sha = merge_request.target_branch_sha
+ return true unless source_sha = merge_request.source_branch_sha
+
+ ref_head.parent_ids != [target_sha, source_sha]
+ end
+
+ def can_git_merge?
+ !merge_request.broken? && repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch)
+ end
+
+ def merge_to_ref
+ return true unless merge_ref_auto_sync_enabled?
+
+ result = MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ result[:status] == :success
+ end
+
+ def merge_ref_auto_sync_enabled?
+ Feature.enabled?(:merge_ref_auto_sync, project, default_enabled: true)
+ end
+ end
+end
diff --git a/app/services/service_response.rb b/app/services/service_response.rb
index 1de30e68d87..f3437ba16de 100644
--- a/app/services/service_response.rb
+++ b/app/services/service_response.rb
@@ -1,19 +1,20 @@
# frozen_string_literal: true
class ServiceResponse
- def self.success(message: nil)
- new(status: :success, message: message)
+ def self.success(message: nil, payload: {})
+ new(status: :success, message: message, payload: payload)
end
- def self.error(message:, http_status: nil)
- new(status: :error, message: message, http_status: http_status)
+ def self.error(message:, payload: {}, http_status: nil)
+ new(status: :error, message: message, payload: payload, http_status: http_status)
end
- attr_reader :status, :message, :http_status
+ attr_reader :status, :message, :http_status, :payload
- def initialize(status:, message: nil, http_status: nil)
+ def initialize(status:, message: nil, payload: {}, http_status: nil)
self.status = status
self.message = message
+ self.payload = payload
self.http_status = http_status
end
@@ -27,5 +28,5 @@ class ServiceResponse
private
- attr_writer :status, :message, :http_status
+ attr_writer :status, :message, :http_status, :payload
end
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 438340464bd..1d7a501e5c2 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,5 +1,5 @@
%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } }
- = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do
+ = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", id: "js-onboarding-new-project-link", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do
= sprite_icon('plus-square', size: 16)
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.dropdown-menu-right
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 49ff976f8e8..e401488ecff 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -50,7 +50,7 @@
= link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
.nav-icon-container
= sprite_icon('doc-text')
- %span.nav-item-name
+ %span.nav-item-name#js-onboarding-repo-link
= _('Repository')
%ul.sidebar-sub-level-items
@@ -64,11 +64,11 @@
= _('Files')
= nav_link(controller: [:commit, :commits]) do
- = link_to project_commits_path(@project, current_ref) do
+ = link_to project_commits_path(@project, current_ref), id: 'js-onboarding-commits-link' do
= _('Commits')
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project), class: 'qa-branches-link' do
+ = link_to project_branches_path(@project), class: 'qa-branches-link', id: 'js-onboarding-branches-link' do
= _('Branches')
= nav_link(controller: [:tags]) do
@@ -98,7 +98,7 @@
= link_to project_issues_path(@project), class: 'shortcuts-issues qa-issues-item' do
.nav-icon-container
= sprite_icon('issues')
- %span.nav-item-name
+ %span.nav-item-name#js-onboarding-issues-link
= _('Issues')
- if @project.issues_enabled?
%span.badge.badge-pill.count.issue_counter
@@ -153,7 +153,7 @@
= link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests qa-merge-requests-link' do
.nav-icon-container
= sprite_icon('git-merge')
- %span.nav-item-name
+ %span.nav-item-name#js-onboarding-mr-link
= _('Merge Requests')
%span.badge.badge-pill.count.merge_counter.js-merge-counter
= number_with_delimiter(@project.open_merge_requests_count)
@@ -170,7 +170,7 @@
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines' do
.nav-icon-container
= sprite_icon('rocket')
- %span.nav-item-name
+ %span.nav-item-name#js-onboarding-pipelines-link
= _('CI / CD')
%ul.sidebar-sub-level-items
@@ -335,7 +335,7 @@
= link_to edit_project_path(@project), class: 'shortcuts-tree' do
.nav-icon-container
= sprite_icon('settings')
- %span.nav-item-name.qa-settings-item
+ %span.nav-item-name.qa-settings-item#js-onboarding-settings-link
= _('Settings')
%ul.sidebar-sub-level-items
@@ -351,7 +351,7 @@
%span
= _('General')
= nav_link(controller: :project_members) do
- = link_to project_project_members_path(@project), title: _('Members'), class: 'qa-link-members-settings' do
+ = link_to project_project_members_path(@project), title: _('Members'), class: 'qa-link-members-settings', id: 'js-onboarding-settings-members-link' do
%span
= _('Members')
- if can_edit
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index a5eaae2dff4..3638334d61c 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,6 +1,5 @@
- merged = local_assigns.fetch(:merged, false)
- commit = @repository.commit(branch.dereferenced_target)
-- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_distance = diverging_commit_counts[:distance]
- number_commits_behind = diverging_commit_counts[:behind]
@@ -31,23 +30,7 @@
= s_('Branches|Cant find HEAD commit for this branch')
- if branch.name != @repository.root_ref
- - if number_commits_distance.nil?
- .divergence-graph.d-none.d-md-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind),
- default_branch: @repository.root_ref,
- number_commits_ahead: diverging_count_label(number_commits_ahead) } }
- .graph-side
- .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
- %span.count.count-behind= diverging_count_label(number_commits_behind)
- .graph-separator
- .graph-side
- .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
- %span.count.count-ahead= diverging_count_label(number_commits_ahead)
- - else
- .divergence-graph.d-none.d-md-block{ title: s_('More than %{number_commits_distance} commits different with %{default_branch}') % { number_commits_distance: diverging_count_label(number_commits_distance),
- default_branch: @repository.root_ref} }
- .graph-side.full
- .bar{ style: "width: #{number_commits_distance * bar_graph_width_factor}%" }
- %span.count= diverging_count_label(number_commits_distance)
+ .js-branch-divergence-graph{ data: { distance: number_commits_distance, ahead_count: number_commits_ahead, behind_count: number_commits_behind, default_branch: @repository.root_ref, max_commits: @max_commits } }
.controls.d-none.d-md-block<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name)
@@ -56,7 +39,7 @@
- if branch.name != @repository.root_ref
= link_to project_compare_path(@project, @repository.root_ref, branch.name),
- class: "btn btn-default #{'prepend-left-10' unless merge_project}",
+ class: "btn btn-default js-onboarding-compare-branches #{'prepend-left-10' unless merge_project}",
title: s_('Branches|Compare') do
= s_('Branches|Compare')
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 87b9920e8b4..2c78e74be2f 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -20,9 +20,9 @@
.commit-detail.flex-list
.commit-content.qa-commit-content
- if view_details && merge_request
- = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
+ = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title js-onboarding-commit-item"
- else
- = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
+ = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title js-onboarding-commit-item")
%span.commit-row-message.d-inline.d-sm-none
&middot;
= commit.short_id
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 9293aa1b309..4759991449e 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -6,7 +6,7 @@
.issuable-info-container
.issuable-main-info
.issue-title.title
- %span.issue-title-text{ dir: "auto" }
+ %span.issue-title-text.js-onboarding-issue-item{ dir: "auto" }
- if issue.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(issue)
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 457b2936278..52bb797b5b3 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -2,6 +2,7 @@
- can_create_merge_request = can?(current_user, :create_merge_request_in, @project)
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
+ - value = can_create_confidential_merge_request? ? _('Create confidential merge request') : value
- can_create_path = can_create_branch_project_issue_path(@project, @issue)
- create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch)
@@ -23,12 +24,15 @@
= icon('caret-down')
.droplab-dropdown
- %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-right.gl-show-field-errors{ data: { dropdown: true } }
+ %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-right.gl-show-field-errors{ class: ("create-confidential-merge-request-dropdown-menu" if can_create_confidential_merge_request?), data: { dropdown: true } }
- if can_create_merge_request
%li.droplab-item-selected{ role: 'button', data: { value: 'create-mr', text: _('Create merge request') } }
.menu-item
= icon('check', class: 'icon')
- = _('Create merge request and branch')
+ - if can_create_confidential_merge_request?
+ = _('Create confidential merge request and branch')
+ - else
+ = _('Create merge request and branch')
%li{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: _('Create branch') } }
.menu-item
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index d55afee4523..8ec07dc3bb4 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -91,7 +91,7 @@
= render 'award_emoji/awards_block', awardable: @issue, inline: true
.col-md-12.col-lg-6.new-branch-col
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
- = render 'new_branch' unless @issue.confidential?
+ = render 'new_branch' if show_new_branch_button?
= render_if_exists 'projects/issues/discussion'
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 67e5e4ca62d..eb516684e52 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -6,7 +6,7 @@
.issuable-info-container
.issuable-main-info
.merge-request-title.title
- %span.merge-request-title-text
+ %span.merge-request-title-text.js-onboarding-mr-item
= link_to merge_request.title, merge_request_path(merge_request)
- if merge_request.tasks?
%span.task-status.d-none.d-sm-inline-block
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index aa30ebdc3b8..de1b95692d6 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -4,7 +4,7 @@
= @service.title
= boolean_to_icon @service.activated?
- %p= @service.description
+ %p= #{@service.description}.
- if @service.respond_to?(:detailed_description)
%p= @service.detailed_description
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 5847751b268..b351ecd4edf 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,5 +1,5 @@
- project = find_project_for_result_blob(projects, wiki_blob)
- wiki_blob = parse_search_result(wiki_blob)
-- wiki_blob_link = project_wiki_path(project, Pathname.new(wiki_blob.filename).sub_ext(''))
+- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index fd0cc5fb24e..e55962b629e 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -9,6 +9,7 @@
- cronjob:import_export_project_cleanup
- cronjob:pages_domain_verification_cron
- cronjob:pages_domain_removal_cron
+- cronjob:pages_domain_ssl_renewal_cron
- cronjob:pipeline_schedule
- cronjob:prune_old_events
- cronjob:remove_expired_group_links
@@ -133,6 +134,7 @@
- new_note
- pages
- pages_domain_verification
+- pages_domain_ssl_renewal
- plugin
- post_receive
- process_commit
diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb
index 9a865fea621..ac947f3cf38 100644
--- a/app/workers/build_success_worker.rb
+++ b/app/workers/build_success_worker.rb
@@ -9,7 +9,6 @@ class BuildSuccessWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
- create_deployment(build) if build.has_environment?
stop_environment(build) if build.stops_environment?
end
end
@@ -18,17 +17,6 @@ class BuildSuccessWorker
private
##
- # Deprecated:
- # As of 11.5, we started creating a deployment record when ci_builds record is created.
- # Therefore we no longer need to create a deployment, after a build succeeded.
- # We're leaving this code for the transition period, but we can remove this code in 11.6.
- def create_deployment(build)
- build.create_deployment.try do |deployment|
- deployment.succeed
- end
- end
-
- ##
# TODO: This should be processed in DeploymentSuccessWorker once we started storing `action` value in `deployments` records
def stop_environment(build)
build.persisted_environment.fire_state_event(:stop)
diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb
index 926ae2b7286..59de7903c1c 100644
--- a/app/workers/cluster_provision_worker.rb
+++ b/app/workers/cluster_provision_worker.rb
@@ -9,8 +9,6 @@ class ClusterProvisionWorker
cluster.provider.try do |provider|
Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp?
end
-
- ClusterConfigureWorker.perform_async(cluster.id) if cluster.user?
end
end
end
diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
new file mode 100644
index 00000000000..4ca9db922b4
--- /dev/null
+++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class PagesDomainSslRenewalCronWorker
+ include ApplicationWorker
+ include CronjobQueue
+
+ def perform
+ return unless ::Gitlab::LetsEncrypt::Client.new.enabled?
+
+ PagesDomain.need_auto_ssl_renewal.find_each do |domain|
+ PagesDomainSslRenewalWorker.perform_async(domain.id)
+ end
+ end
+end
diff --git a/app/workers/pages_domain_ssl_renewal_worker.rb b/app/workers/pages_domain_ssl_renewal_worker.rb
new file mode 100644
index 00000000000..00c9c4782d8
--- /dev/null
+++ b/app/workers/pages_domain_ssl_renewal_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class PagesDomainSslRenewalWorker
+ include ApplicationWorker
+
+ def perform(domain_id)
+ return unless ::Gitlab::LetsEncrypt::Client.new.enabled?
+
+ domain = PagesDomain.find_by_id(domain_id)
+
+ return unless domain
+
+ ::PagesDomains::ObtainLetsEncryptCertificateService.new(domain).execute
+ end
+end
diff --git a/changelogs/add-name-parameter-to-project-environments-api.yml b/changelogs/add-name-parameter-to-project-environments-api.yml
new file mode 100644
index 00000000000..01d456eb75c
--- /dev/null
+++ b/changelogs/add-name-parameter-to-project-environments-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add `name` and `search` parameters to project environments API
+merge_request: 29385
+author: Lee Tickett
+type: added
diff --git a/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml b/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml
new file mode 100644
index 00000000000..741a0faf469
--- /dev/null
+++ b/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml
@@ -0,0 +1,5 @@
+---
+title: Protect TeamCity builds from triggering when a branch has been deleted. And a MR-option
+merge_request: 29836
+author: Nikolay Novikov, Raphael Tweitmann
+type: fixed
diff --git a/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml b/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml
new file mode 100644
index 00000000000..b51079d5c74
--- /dev/null
+++ b/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: "Changed HTTP Status Code for disabled repository on /branches and /commits to 404"
+merge_request: 29585
+author: Sam Battalio
+type: changed
diff --git a/changelogs/unreleased/52366-improved-group-lists-ui.yml b/changelogs/unreleased/52366-improved-group-lists-ui.yml
new file mode 100644
index 00000000000..99e5b67a56c
--- /dev/null
+++ b/changelogs/unreleased/52366-improved-group-lists-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Improve group list UI
+merge_request: 26542
+author:
+type: changed
diff --git a/changelogs/unreleased/60617-enable-project-cluster-jit.yml b/changelogs/unreleased/60617-enable-project-cluster-jit.yml
new file mode 100644
index 00000000000..b7d745d4385
--- /dev/null
+++ b/changelogs/unreleased/60617-enable-project-cluster-jit.yml
@@ -0,0 +1,5 @@
+---
+title: Enable just-in-time Kubernetes resource creation for project-level clusters
+merge_request: 29515
+author:
+type: changed
diff --git a/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml b/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml
new file mode 100644
index 00000000000..3f7a8e19de5
--- /dev/null
+++ b/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Avatar in Please sign in pattern too large
+merge_request: 29944
+author:
+type: fixed
diff --git a/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml b/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml
new file mode 100644
index 00000000000..08c415f4a1c
--- /dev/null
+++ b/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml
@@ -0,0 +1,5 @@
+---
+title: Fix runner tags search dropdown being empty when there are tags
+merge_request: 29985
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-labels-in-hooks.yml b/changelogs/unreleased/fix-labels-in-hooks.yml
new file mode 100644
index 00000000000..c0904a860c5
--- /dev/null
+++ b/changelogs/unreleased/fix-labels-in-hooks.yml
@@ -0,0 +1,5 @@
+---
+title: Fix label serialization in issue and note hooks
+merge_request: 29850
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-notes-emails-with-group-settings.yml b/changelogs/unreleased/fix-notes-emails-with-group-settings.yml
new file mode 100644
index 00000000000..77dae8418a8
--- /dev/null
+++ b/changelogs/unreleased/fix-notes-emails-with-group-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Fix comment emails not respecting group-level notification email
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml b/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml
new file mode 100644
index 00000000000..2b7e3611567
--- /dev/null
+++ b/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml
@@ -0,0 +1,6 @@
+---
+title: Fix pipeline schedule does not run correctly when it's scheduled at the same
+ time with the cron worker
+merge_request: 29848
+author:
+type: fixed
diff --git a/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml b/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml
new file mode 100644
index 00000000000..d9ca20d9d4d
--- /dev/null
+++ b/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml
@@ -0,0 +1,5 @@
+---
+title: Remove tooltip directive on project avatar image component
+merge_request: 29631
+author: George Tsiolis
+type: performance
diff --git a/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml b/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml
new file mode 100644
index 00000000000..d2744cddebd
--- /dev/null
+++ b/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml
@@ -0,0 +1,5 @@
+---
+title: Sync merge ref upon mergeability check
+merge_request: 29569
+author:
+type: added
diff --git a/changelogs/unreleased/search-blob-basenames.yml b/changelogs/unreleased/search-blob-basenames.yml
new file mode 100644
index 00000000000..48ad1130e3f
--- /dev/null
+++ b/changelogs/unreleased/search-blob-basenames.yml
@@ -0,0 +1,5 @@
+---
+title: Build correct basenames for title search results
+merge_request: 29898
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml b/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml
new file mode 100644
index 00000000000..12f4a5a499d
--- /dev/null
+++ b/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Gitaly ref name caching for discussions.json
+merge_request: 29951
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-optimize-todos-controller.yml b/changelogs/unreleased/sh-optimize-todos-controller.yml
new file mode 100644
index 00000000000..181ddd1b3bc
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-todos-controller.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate N+1 queries in Dashboard::TodosController
+merge_request: 29954
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-strong-memoize-appearances.yml b/changelogs/unreleased/sh-strong-memoize-appearances.yml
new file mode 100644
index 00000000000..dc4fe1c4d8e
--- /dev/null
+++ b/changelogs/unreleased/sh-strong-memoize-appearances.yml
@@ -0,0 +1,5 @@
+---
+title: Memoize non-existent custom appearances
+merge_request: 29957
+author:
+type: performance
diff --git a/changelogs/unreleased/update-pagination-texts.yml b/changelogs/unreleased/update-pagination-texts.yml
new file mode 100644
index 00000000000..6a398e26242
--- /dev/null
+++ b/changelogs/unreleased/update-pagination-texts.yml
@@ -0,0 +1,5 @@
+---
+title: Update pagination prev and next texts
+merge_request: 29911
+author:
+type: other
diff --git a/changelogs/unreleased/update-tar-to-2-2-2.yml b/changelogs/unreleased/update-tar-to-2-2-2.yml
new file mode 100644
index 00000000000..f142fe59448
--- /dev/null
+++ b/changelogs/unreleased/update-tar-to-2-2-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update tar to 2.2.2
+merge_request: 29949
+author: Takuya Noguchi
+type: security
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 6cca7a3b75f..4b0bb86e42a 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -367,6 +367,10 @@ Settings.cron_jobs['pages_domain_removal_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['pages_domain_removal_cron_worker']['cron'] ||= '47 0 * * *'
Settings.cron_jobs['pages_domain_removal_cron_worker']['job_class'] = 'PagesDomainRemovalCronWorker'
+Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker']['cron'] ||= '*/5 * * * *'
+Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker']['job_class'] = 'PagesDomainSslRenewalCronWorker'
+
Settings.cron_jobs['issue_due_scheduler_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['issue_due_scheduler_worker']['cron'] ||= '50 00 * * *'
Settings.cron_jobs['issue_due_scheduler_worker']['job_class'] = 'IssueDueSchedulerWorker'
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 4fda9d69077..25fd65d8644 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -72,6 +72,7 @@
- [project_rollback_hashed_storage, 1]
- [hashed_storage, 1]
- [pages_domain_verification, 1]
+ - [pages_domain_ssl_renewal, 1]
- [object_storage_upload, 1]
- [object_storage, 1]
- [plugin, 1]
diff --git a/db/migrate/20190607145325_add_pages_domains_ssl_renew_index.rb b/db/migrate/20190607145325_add_pages_domains_ssl_renew_index.rb
new file mode 100644
index 00000000000..7167accbf1e
--- /dev/null
+++ b/db/migrate/20190607145325_add_pages_domains_ssl_renew_index.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddPagesDomainsSslRenewIndex < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ INDEX_NAME = 'index_pages_domains_need_auto_ssl_renewal'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:pages_domains, [:certificate_source, :certificate_valid_not_after],
+ where: "auto_ssl_enabled = #{::Gitlab::Database.true_value}", name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index(:pages_domains, [:certificate_source, :certificate_valid_not_after],
+ where: "auto_ssl_enabled = #{::Gitlab::Database.true_value}", name: INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20190620112608_enqueue_reset_merge_status_second_run.rb b/db/post_migrate/20190620112608_enqueue_reset_merge_status_second_run.rb
new file mode 100644
index 00000000000..2d096a2a39c
--- /dev/null
+++ b/db/post_migrate/20190620112608_enqueue_reset_merge_status_second_run.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class EnqueueResetMergeStatusSecondRun < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+ MIGRATION = 'ResetMergeStatus'
+ DELAY_INTERVAL = 5.minutes.to_i
+
+ disable_ddl_transaction!
+
+ def up
+ say 'Scheduling `ResetMergeStatus` jobs'
+
+ # We currently have more than ~5_000_000 merge request records on GitLab.com.
+ # This means it'll schedule ~500 jobs (10k MRs each) with a 5 minutes gap,
+ # so this should take ~41 hours for all background migrations to complete.
+ # ((5_000_000 / 10_000) * 5) / 60 => 41.6666..
+ queue_background_migration_jobs_by_range_at_intervals(MergeRequest, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c1c67e012e9..b81558178b9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190619175843) do
+ActiveRecord::Schema.define(version: 20190620112608) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -2334,6 +2334,7 @@ ActiveRecord::Schema.define(version: 20190619175843) do
t.datetime_with_timezone "certificate_valid_not_before"
t.datetime_with_timezone "certificate_valid_not_after"
t.integer "certificate_source", limit: 2, default: 0, null: false
+ t.index ["certificate_source", "certificate_valid_not_after"], name: "index_pages_domains_need_auto_ssl_renewal", where: "(auto_ssl_enabled = true)", using: :btree
t.index ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
t.index ["project_id", "enabled_until"], name: "index_pages_domains_on_project_id_and_enabled_until", using: :btree
t.index ["project_id"], name: "index_pages_domains_on_project_id", using: :btree
diff --git a/doc/api/environments.md b/doc/api/environments.md
index ebcdc546d08..44f86861ff7 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -11,9 +11,11 @@ GET /projects/:id/environments
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `name` | string | no | Return the environment with this name. Mutually exclusive with `search` |
+| `search` | string | no | Return list of environments matching the search criteria. Mutually exclusive with `name` |
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/environments
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/environments?name=review%2Ffix-foo
```
Example response:
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index dd7810c3403..7b58aa3100e 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -1191,33 +1191,29 @@ Parameters:
}
```
-## Merge to default merge ref path
+## Returns the up to date merge-ref HEAD commit
Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge`
-ref, of the target project repository. This ref will have the state the target branch would have if
+ref, of the target project repository, if possible. This ref will have the state the target branch would have if
a regular merge action was taken.
-This is not a regular merge action given it doesn't change the merge request state in any manner.
+This is not a regular merge action given it doesn't change the merge request target branch state in any manner.
-This ref (`refs/merge-requests/:iid/merge`) is **always** overwritten when submitting
-requests to this API, so none of its state is kept or used in the process.
+This ref (`refs/merge-requests/:iid/merge`) isn't necessarily overwritten when submitting
+requests to this API, though it'll make sure the ref has the latest possible state.
-If the merge request has conflicts, is empty or already merged,
-you'll get a `400` and a descriptive error message. If you don't have permissions to do so,
-you'll get a `403`.
+If the merge request has conflicts, is empty or already merged, you'll get a `400` and a descriptive error message.
-It returns the HEAD commit of `refs/merge-requests/:iid/merge` in the response body in
-case of `200`.
+It returns the HEAD commit of `refs/merge-requests/:iid/merge` in the response body in case of `200`.
```
-PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref
+GET /projects/:id/merge_requests/:merge_request_iid/merge_ref
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - Internal ID of MR
-- `merge_commit_message` (optional) - Custom merge commit message
```json
{
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index df455857dee..c8c92002be2 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -27,8 +27,7 @@ CI/CD's pipelines. Using variables means no hardcoded values.
### Predefined environment variables
-GitLab CI/CD has a default set of
-[predefined variables](predefined_variables.md)
+GitLab CI/CD has a [default set of predefined variables](predefined_variables.md)
which can be used without any specification needed.
You can call issues numbers, user names, branch names,
pipeline and commit IDs, and much more.
@@ -36,7 +35,7 @@ pipeline and commit IDs, and much more.
Predefined environment variables are the ones that GitLab
provides out of the box for the local environment of the Runner.
-GitLab reads the .gitlab-ci.yml file, sends the information
+GitLab reads the `.gitlab-ci.yml` file, sends the information
to the Runner (which runs the script commands), under which
the variables are exposed.
@@ -44,6 +43,9 @@ For example, two jobs under the same pipeline can share the same
`CI_PIPELINE_ID` variable, but each one has its own `CI_JOB_ID`
variable.
+NOTE: **Note:**
+Find here the full [**predefined variables reference table**](predefined_variables.md).
+
### Custom environment variables
When your use case requires a specific variable, you can
@@ -480,7 +482,7 @@ Below you can find supported syntax reference:
> Example: `$VARIABLE == "some value"`
- > Example: `$VARIABLE != "some value"` _(added in 11.11)_
+ > Example: `$VARIABLE != "some value"` (introduced in GitLab 11.11)
You can use equality operator `==` or `!=` to compare a variable content to a
string. We support both, double quotes and single quotes to define a string
@@ -491,7 +493,7 @@ Below you can find supported syntax reference:
> Example: `$VARIABLE == null`
- > Example: `$VARIABLE != null` _(added in 11.11)_
+ > Example: `$VARIABLE != null` (introduced in GitLab 11.11)
It sometimes happens that you want to check whether a variable is defined
or not. To do that, you can compare a variable to `null` keyword, like
@@ -502,7 +504,7 @@ Below you can find supported syntax reference:
> Example: `$VARIABLE == ""`
- > Example: `$VARIABLE != ""` _(added in 11.11)_
+ > Example: `$VARIABLE != ""` (introduced in GitLab 11.11)
If you want to check whether a variable is defined, but is empty, you can
simply compare it against an empty string, like `$VAR == ''` or non-empty
@@ -512,7 +514,7 @@ Below you can find supported syntax reference:
> Example: `$VARIABLE_1 == $VARIABLE_2`
- > Example: `$VARIABLE_1 != $VARIABLE_2` _(added in 11.11)_
+ > Example: `$VARIABLE_1 != $VARIABLE_2` (introduced in GitLab 11.11)
It is possible to compare two variables. This is going to compare values
of these variables.
@@ -528,11 +530,11 @@ Below you can find supported syntax reference:
`$STAGING` value needs to a string, with length higher than zero.
Variable that contains only whitespace characters is not an empty variable.
-1. Pattern matching _(added in 11.0)_
+1. Pattern matching (introduced in GitLab 11.0)
> Example: `$VARIABLE =~ /^content.*/`
- > Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_
+ > Example: `$VARIABLE_1 !~ /^content.*/` (introduced in GitLab 11.11)
It is possible perform pattern matching against a variable and regular
expression. Expression like this evaluates to truth if matches are found
@@ -541,7 +543,7 @@ Below you can find supported syntax reference:
Pattern matching is case-sensitive by default. Use `i` flag modifier, like
`/pattern/i` to make a pattern case-insensitive.
-1. Conjunction / Disjunction
+1. Conjunction / Disjunction ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27925) in GitLab 12.0)
> Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 29e2aa1a581..6123f9f845a 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -60,6 +60,8 @@ from teams other than your own.
1. If your merge request includes backend changes [^1], it must be
**approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**.
+ 1. If your merge request includes database migrations or changes to expensive queries [^2], it must be
+ **approved by a [database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_database)**.
1. If your merge request includes frontend changes [^1], it must be
**approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**.
1. If your merge request includes UX changes [^1], it must be
@@ -377,3 +379,4 @@ Largely based on the [thoughtbot code review guide].
[team]: https://about.gitlab.com/team/
[build handbook]: https://about.gitlab.com/handbook/build/handbook/build#how-to-work-with-build
[^1]: Please note that specs other than JavaScript specs are considered backend code.
+[^2]: We encourage you to seek guidance from a database maintainer if your merge request is potentially introducing expensive queries. It is most efficient to comment on the line of code in question with the SQL queries so they can give their advice.
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index b2f3a105b23..795523b82aa 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -38,10 +38,6 @@ For routes that require authorization you will need to provide a user to
Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first)
```
-The user you provide will need to have a [personal access
-token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) in
-the GitLab instance.
-
Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send
ActiveRecord and ActionController log output to that logger. Further options are
documented with the method source.
diff --git a/doc/development/reusing_abstractions.md b/doc/development/reusing_abstractions.md
index 01cedf734fb..59da02ed6fd 100644
--- a/doc/development/reusing_abstractions.md
+++ b/doc/development/reusing_abstractions.md
@@ -127,6 +127,42 @@ Everything in `lib/api`.
Everything that resides in `app/services`.
+#### ServiceResponse
+
+Service classes usually have an `execute` method, which can return a
+`ServiceResponse`. You can use `ServiceResponse.success` and
+`ServiceResponse.error` to return a response in `execute` method.
+
+In a successful case:
+
+``` ruby
+response = ServiceResponse.success(message: 'Branch was deleted')
+
+response.success? # => true
+response.error? # => false
+response.status # => :success
+response.message # => 'Branch was deleted'
+```
+
+In a failed case:
+
+``` ruby
+response = ServiceResponse.error(message: 'Unsupported operation')
+
+response.success? # => false
+response.error? # => true
+response.status # => :error
+response.message # => 'Unsupported operation'
+```
+
+An additional payload can also be attached:
+
+``` ruby
+response = ServiceResponse.success(payload: { issue: issue })
+
+response.payload[:issue] # => issue
+```
+
### Finders
Everything in `app/finders`, typically used for retrieving data from a database.
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index a1229484388..63879935fd8 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -4,12 +4,15 @@ type: reference
# Visibility and access controls
-GitLab allows admins to:
+GitLab allows administrators to:
- Control access and visibility to GitLab resources including branches and projects.
- Select from which hosting sites code can be imported into GitLab.
- Select the protocols permitted to access GitLab.
- Enable or disable repository mirroring.
+- Prevent non-administrators from deleting projects
+ ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5615) in GitLab 12.0).
+ **[PREMIUM ONLY]**
To access the visibility and access control options:
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index abd95eddf63..4fde45da6c4 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -268,9 +268,10 @@ be unique.
To change your group path:
-1. Navigate to your group's **Settings > General**.
-1. Enter a new name under **Group path**.
-1. Click **Save group**.
+1. Navigate to your group's **Settings > General** page.
+1. Expand the **Path, transfer, remove** section.
+1. Enter a new name under **Change group path**.
+1. Click **Change group path**.
CAUTION: **Caution:**
It is currently not possible to rename a namespace if it contains a
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 547a0c36108..a0fe97f2b9d 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -520,9 +520,7 @@ service account of the cluster integration.
### Troubleshooting failed deployment jobs
GitLab will create a namespace and service account specifically for your
-deployment jobs. On project level clusters, this happens when the cluster
-is created. On group level clusters, resources are created immediately
-before the deployment job starts.
+deployment jobs. This happens immediately before the deployment job starts.
However, sometimes GitLab can not create them. In such instances, your job will fail with the message:
diff --git a/doc/user/project/deploy_boards.md b/doc/user/project/deploy_boards.md
index 2aef369c087..175384bc985 100644
--- a/doc/user/project/deploy_boards.md
+++ b/doc/user/project/deploy_boards.md
@@ -88,7 +88,10 @@ To display the Deploy Boards for a specific [environment] you should:
Kubernetes.
NOTE: **Note:**
- Matching based on the Kubernetes `app` label was removed in [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14020)
+ Matching based on the Kubernetes `app` label was removed in [GitLab
+ 12.1](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14020).
+ To migrate, please apply the required annotations (see above) and
+ re-deploy your application.
![Deploy Boards Kubernetes Label](img/deploy_boards_kubernetes_label.png)
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 65d7f68bbf9..c3821630b6b 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -8,7 +8,10 @@ module API
BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
- before { authorize! :download_code, user_project }
+ before do
+ require_repository_enabled!
+ authorize! :download_code, user_project
+ end
helpers do
params :filter_params do
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 80913f4ca07..eebded87ebc 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -6,7 +6,10 @@ module API
class Commits < Grape::API
include PaginationParams
- before { authorize! :download_code, user_project }
+ before do
+ require_repository_enabled!
+ authorize! :download_code, user_project
+ end
helpers do
def user_access
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 25e9fdd5fce..ead01dc53f7 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -703,7 +703,7 @@ module API
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more
# information.
expose :merge_status do |merge_request|
- merge_request.check_if_can_be_merged
+ merge_request.check_mergeability
merge_request.merge_status
end
expose :diff_head_sha, as: :sha
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 6cd43923559..ec58b3b7bb9 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -18,11 +18,16 @@ module API
end
params do
use :pagination
+ optional :name, type: String, desc: 'Returns the environment with this name'
+ optional :search, type: String, desc: 'Returns list of environments matching the search criteria'
+ mutually_exclusive :name, :search, message: 'cannot be used together'
end
get ':id/environments' do
authorize! :read_environment, user_project
- present paginate(user_project.environments), with: Entities::Environment, current_user: current_user
+ environments = ::EnvironmentsFinder.new(user_project, current_user, params).find
+
+ present paginate(environments), with: Entities::Environment, current_user: current_user
end
desc 'Creates a new environment' do
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6382d295f79..8ae42c6dadd 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -250,6 +250,10 @@ module API
authorize! :update_build, user_project
end
+ def require_repository_enabled!(subject = :global)
+ not_found!("Repository") unless user_project.feature_available?(:repository, current_user)
+ end
+
def require_gitlab_workhorse!
unless env['HTTP_GITLAB_WORKHORSE'].present?
forbidden!('Request should be executed via GitLab Workhorse')
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 955624404f1..bf87e9ec2ff 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -397,28 +397,16 @@ module API
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
- desc 'Merge a merge request to its default temporary merge ref path'
- params do
- optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
- end
- put ':id/merge_requests/:merge_request_iid/merge_to_ref' do
+ desc 'Returns the up to date merge-ref HEAD commit'
+ get ':id/merge_requests/:merge_request_iid/merge_ref' do
merge_request = find_project_merge_request(params[:merge_request_iid])
- authorize! :admin_merge_request, user_project
-
- merge_params = {
- commit_message: params[:merge_commit_message]
- }
-
- result = ::MergeRequests::MergeToRefService
- .new(merge_request.target_project, current_user, merge_params)
- .execute(merge_request)
+ result = ::MergeRequests::MergeabilityCheckService.new(merge_request).execute(recheck: true)
- if result[:status] == :success
- present result.slice(:commit_id), 200
+ if result.success?
+ present :commit_id, result.payload.dig(:merge_ref_head, :commit_id)
else
- http_status = result[:http_status] || 400
- render_api_error!(result[:message], http_status)
+ render_api_error!(result.message, 400)
end
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index d2196f05173..871eaabc887 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -77,7 +77,7 @@ module API
use :pagination
end
get do
- todos = paginate(find_todos.with_api_entity_associations)
+ todos = paginate(find_todos.with_entity_associations)
options = { with: Entities::Todo, current_user: current_user }
batch_load_issuable_metadata(todos, options)
diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb
index 55411b5a8c4..fc3223e7442 100644
--- a/lib/gitlab/ci/ansi2html.rb
+++ b/lib/gitlab/ci/ansi2html.rb
@@ -200,9 +200,7 @@ module Gitlab
css_classes = %w[section line] + sections.map { |section| "s_#{section}" }
end
- ensure_open_new_tag
- write_raw %{<br/>}
- close_open_tags
+ write_in_tag %{<br/>}
write_raw %{<span class="#{css_classes.join(' ')}"></span>} if css_classes.any?
@lineno_in_section += 1
open_new_tag
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
index 472492b6bfa..49c680605ea 100644
--- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -8,7 +8,6 @@ module Gitlab
def unmet?
deployment_cluster.present? &&
deployment_cluster.managed? &&
- !deployment_cluster.project_type? &&
(kubernetes_namespace.new_record? || kubernetes_namespace.service_account_token.blank?)
end
diff --git a/lib/gitlab/data_builder/note.rb b/lib/gitlab/data_builder/note.rb
index 16e62622ed4..2c4ef73a688 100644
--- a/lib/gitlab/data_builder/note.rb
+++ b/lib/gitlab/data_builder/note.rb
@@ -44,7 +44,7 @@ module Gitlab
data[:commit] = build_data_for_commit(project, user, note)
elsif note.for_issue?
data[:issue] = note.noteable.hook_attrs
- data[:issue][:labels] = note.noteable.labels(&:hook_attrs)
+ data[:issue][:labels] = note.noteable.labels_hook_attrs
elsif note.for_merge_request?
data[:merge_request] = note.noteable.hook_attrs
elsif note.for_snippet?
diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb
index cfc9ebe4f92..e5f86ca02b5 100644
--- a/lib/gitlab/hook_data/issue_builder.rb
+++ b/lib/gitlab/hook_data/issue_builder.rb
@@ -45,7 +45,7 @@ module Gitlab
human_time_estimate: issue.human_time_estimate,
assignee_ids: issue.assignee_ids,
assignee_id: issue.assignee_ids.first, # This key is deprecated
- labels: issue.labels
+ labels: issue.labels_hook_attrs
}
issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
diff --git a/lib/gitlab/search/found_blob.rb b/lib/gitlab/search/found_blob.rb
index a62ab1521a7..01ce90c85f7 100644
--- a/lib/gitlab/search/found_blob.rb
+++ b/lib/gitlab/search/found_blob.rb
@@ -93,7 +93,7 @@ module Gitlab
data = {
id: blob.id,
binary_filename: blob.path,
- binary_basename: File.basename(blob.path, File.extname(blob.path)),
+ binary_basename: path_without_extension(blob.path),
ref: ref,
startline: 1,
binary_data: blob.data,
@@ -111,6 +111,10 @@ module Gitlab
content_match.match(FILENAME_REGEXP) { |matches| matches[:filename] }
end
+ def path_without_extension(path)
+ Pathname.new(path).sub_ext('').to_s
+ end
+
def parsed_content
strong_memoize(:parsed_content) do
if content_match
@@ -137,8 +141,7 @@ module Gitlab
filename = matches[:filename]
startline = matches[:startline]
startline = startline.to_i - index
- extname = Regexp.escape(File.extname(filename))
- basename = filename.sub(/#{extname}$/, '')
+ basename = path_without_extension(filename)
end
data << line.sub(prefix.to_s, '')
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ed06389cf05..97613a6a920 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16,6 +16,9 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
+msgid " (from %{timeoutSource})"
+msgstr ""
+
msgid " Please sign in."
msgstr ""
@@ -3056,6 +3059,12 @@ msgstr ""
msgid "Create commit"
msgstr ""
+msgid "Create confidential merge request"
+msgstr ""
+
+msgid "Create confidential merge request and branch"
+msgstr ""
+
msgid "Create directory"
msgstr ""
@@ -6943,13 +6952,13 @@ msgstr ""
msgid "Pagination|Last »"
msgstr ""
-msgid "Pagination|Next"
+msgid "Pagination|Next ›"
msgstr ""
-msgid "Pagination|Prev"
+msgid "Pagination|« First"
msgstr ""
-msgid "Pagination|« First"
+msgid "Pagination|‹ Prev"
msgstr ""
msgid "Parameter"
@@ -9175,6 +9184,9 @@ msgid_plural "Showing %d events"
msgstr[0] ""
msgstr[1] ""
+msgid "Showing last %{size} of log -"
+msgstr ""
+
msgid "Side-by-side"
msgstr ""
diff --git a/package.json b/package.json
index 54998cc81dd..8e97419bfcb 100644
--- a/package.json
+++ b/package.json
@@ -87,7 +87,7 @@
"imports-loader": "^0.8.0",
"jed": "^1.1.1",
"jest-transform-graphql": "^2.1.0",
- "jquery": "^3.2.1",
+ "jquery": "^3.4.1",
"jquery-ujs": "1.2.2",
"jquery.caret": "^0.3.1",
"jquery.waitforimages": "^2.2.0",
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index 68850d989b1..fae7818f871 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -4,7 +4,7 @@ module QA::Page
module Project::Pipeline
class Index < QA::Page::Base
view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do
- element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern
+ element :pipeline_link, 'class="js-pipeline-url-link' # rubocop:disable QA/ElementWithPattern
end
def click_on_latest_pipeline
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 6243ddc03c0..9a3fbfaac51 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -44,6 +44,34 @@ describe Dashboard::TodosController do
end
end
+ context "with render_views" do
+ render_views
+
+ it 'avoids N+1 queries', :request_store do
+ merge_request = create(:merge_request, source_project: project)
+ create(:todo, project: project, author: author, user: user, target: merge_request)
+ create(:issue, project: project, assignees: [user])
+
+ group = create(:group)
+ group.add_owner(user)
+
+ get :index
+
+ control = ActiveRecord::QueryRecorder.new { get :index }
+
+ create(:issue, project: project, assignees: [user])
+ group_2 = create(:group)
+ group_2.add_owner(user)
+ project_2 = create(:project)
+ project_2.add_developer(user)
+ merge_request_2 = create(:merge_request, source_project: project_2)
+ create(:todo, project: project, author: author, user: user, target: merge_request_2)
+
+ expect { get :index }.not_to exceed_query_limit(control)
+ expect(response.status).to eq(200)
+ end
+ end
+
context 'group authorization' do
it 'renders 404 when user does not have read access on given group' do
unauthorized_group = create(:group, :private)
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index fa49438287f..35cbab57037 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -340,7 +340,6 @@ describe Projects::ClustersController do
describe 'security' do
before do
- allow(ClusterConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
@@ -438,7 +437,6 @@ describe Projects::ClustersController do
end
before do
- allow(ClusterConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 34cbf0c8723..0eca663a683 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1001,6 +1001,8 @@ describe Projects::MergeRequestsController do
before do
project.add_developer(user)
sign_in(user)
+
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
end
it 'returns 200' do
diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb
index 8da19a37a6a..3e0baab04ce 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -182,6 +182,7 @@ ZDXgrA==
end
trait :letsencrypt do
+ auto_ssl_enabled { true }
certificate_source { :gitlab_provided }
end
end
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index b69fba0db00..f9103d83ba0 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
describe 'User creates branch and merge request on issue page', :js do
let(:membership_level) { :developer }
let(:user) { create(:user) }
- let!(:project) { create(:project, :repository) }
+ let!(:project) { create(:project, :repository, :public) }
let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') }
context 'when signed out' do
@@ -163,10 +163,21 @@ describe 'User creates branch and merge request on issue page', :js do
let(:issue) { create(:issue, :confidential, project: project) }
it 'disables the create branch button' do
+ stub_feature_flags(create_confidential_merge_request: false)
+
visit project_issue_path(project, issue)
expect(page).not_to have_css('.create-mr-dropdown-wrap')
end
+
+ it 'enables the create branch button when feature flag is enabled' do
+ stub_feature_flags(create_confidential_merge_request: true)
+
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('.create-mr-dropdown-wrap')
+ expect(page).to have_button('Create confidential merge request')
+ end
end
context 'when related branch exists' do
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index e4a3a1a8c92..974e0f84681 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -118,7 +118,6 @@ describe 'Gcp Cluster', :js do
context 'when user changes cluster parameters' do
before do
- allow(ClusterConfigureWorker).to receive(:perform_async)
fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
page.within('#js-cluster-details') { click_button 'Save changes' }
end
diff --git a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb
index 28d83a8b961..c50fd93e4cb 100644
--- a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb
+++ b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb
@@ -15,6 +15,8 @@ describe 'User activates JetBrains TeamCity CI' do
it 'activates service' do
check('Active')
+ check('Push')
+ check('Merge request')
fill_in('Teamcity url', with: 'http://teamcity.example.com')
fill_in('Build type', with: 'GitlabTest_Build')
fill_in('Username', with: 'user')
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 6d4facd0649..ee43755262e 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'User searches for wiki pages', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
- let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'test_wiki', content: 'Some Wiki content' }) }
+ let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'directory/title', content: 'Some Wiki content' }) }
before do
project.add_maintainer(user)
@@ -22,7 +22,7 @@ describe 'User searches for wiki pages', :js do
click_link(project.full_name)
end
- fill_in('dashboard_search', with: 'content')
+ fill_in('dashboard_search', with: search_term)
find('.btn-search').click
page.within('.search-filter') do
@@ -43,7 +43,7 @@ describe 'User searches for wiki pages', :js do
context 'when searching by title' do
it_behaves_like 'search wiki blobs' do
- let(:search_term) { 'test_wiki' }
+ let(:search_term) { 'title' }
end
end
end
diff --git a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb
index 79d2f9cdb45..c4e6c9cc9f5 100644
--- a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb
+++ b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb
@@ -17,13 +17,13 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do
context 'filter by search' do
context 'with an empty search term' do
- it 'returns an empty collection' do
- ActsAsTaggableOn::Tag.create!(name: 'tag1')
- ActsAsTaggableOn::Tag.create!(name: 'tag2')
+ it 'returns all tags' do
+ tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1')
+ tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag2')
tags = described_class.new(params: { search: '' }).execute
- expect(tags).to be_empty
+ expect(tags).to match_array [tag1, tag2]
end
end
diff --git a/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap b/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap
new file mode 100644
index 00000000000..511c027dbc2
--- /dev/null
+++ b/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap
@@ -0,0 +1,37 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Branch divergence graph component renders ahead and behind count 1`] = `
+<div
+ class="divergence-graph px-2 d-none d-md-block"
+ title="10 commits behind master, 10 commits ahead"
+>
+ <graphbar-stub
+ count="10"
+ maxcommits="100"
+ position="left"
+ />
+
+ <div
+ class="graph-separator pull-left mt-1"
+ />
+
+ <graphbar-stub
+ count="10"
+ maxcommits="100"
+ position="right"
+ />
+</div>
+`;
+
+exports[`Branch divergence graph component renders distance count 1`] = `
+<div
+ class="divergence-graph px-2 d-none d-md-block"
+ title="More than 900 commits different with master"
+>
+ <graphbar-stub
+ count="900"
+ maxcommits="100"
+ position="full"
+ />
+</div>
+`;
diff --git a/spec/frontend/branches/components/divergence_graph_spec.js b/spec/frontend/branches/components/divergence_graph_spec.js
new file mode 100644
index 00000000000..b54b2ceb233
--- /dev/null
+++ b/spec/frontend/branches/components/divergence_graph_spec.js
@@ -0,0 +1,67 @@
+import { shallowMount } from '@vue/test-utils';
+import DivergenceGraph from '~/branches/components/divergence_graph.vue';
+import GraphBar from '~/branches/components/graph_bar.vue';
+
+let vm;
+
+function factory(propsData = {}) {
+ vm = shallowMount(DivergenceGraph, { propsData });
+}
+
+describe('Branch divergence graph component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders ahead and behind count', () => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 10,
+ behindCount: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.findAll(GraphBar).length).toBe(2);
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it('sets title for ahead and behind count', () => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 10,
+ behindCount: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.attributes('title')).toBe('10 commits behind master, 10 commits ahead');
+ });
+
+ it('renders distance count', () => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 0,
+ behindCount: 0,
+ distance: 900,
+ maxCommits: 100,
+ });
+
+ expect(vm.findAll(GraphBar).length).toBe(1);
+ expect(vm.element).toMatchSnapshot();
+ });
+
+ it.each`
+ distance | titleText
+ ${900} | ${'900'}
+ ${1100} | ${'999+'}
+ `('sets title for $distance as $titleText', ({ distance, titleText }) => {
+ factory({
+ defaultBranch: 'master',
+ aheadCount: 0,
+ behindCount: 0,
+ distance,
+ maxCommits: 100,
+ });
+
+ expect(vm.attributes('title')).toBe(`More than ${titleText} commits different with master`);
+ });
+});
diff --git a/spec/frontend/branches/components/graph_bar_spec.js b/spec/frontend/branches/components/graph_bar_spec.js
new file mode 100644
index 00000000000..61c051b49c6
--- /dev/null
+++ b/spec/frontend/branches/components/graph_bar_spec.js
@@ -0,0 +1,89 @@
+import { shallowMount } from '@vue/test-utils';
+import GraphBar from '~/branches/components/graph_bar.vue';
+
+let vm;
+
+function factory(propsData = {}) {
+ vm = shallowMount(GraphBar, { propsData });
+}
+
+describe('Branch divergence graph bar component', () => {
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it.each`
+ position | positionClass
+ ${'left'} | ${'position-right-0'}
+ ${'right'} | ${'position-left-0'}
+ ${'full'} | ${'position-left-0'}
+ `(
+ 'sets position class as $positionClass for position $position',
+ ({ position, positionClass }) => {
+ factory({
+ position,
+ count: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.find('.js-graph-bar').classes()).toContain(positionClass);
+ },
+ );
+
+ it.each`
+ position | textAlignmentClass
+ ${'left'} | ${'text-right'}
+ ${'right'} | ${'text-left'}
+ ${'full'} | ${'text-center'}
+ `(
+ 'sets text alignment class as $textAlignmentClass for position $position',
+ ({ position, textAlignmentClass }) => {
+ factory({
+ position,
+ count: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.find('.js-graph-count').classes()).toContain(textAlignmentClass);
+ },
+ );
+
+ it.each`
+ position | roundedClass
+ ${'left'} | ${'rounded-left'}
+ ${'right'} | ${'rounded-right'}
+ ${'full'} | ${'rounded'}
+ `('sets rounded class as $roundedClass for position $position', ({ position, roundedClass }) => {
+ factory({
+ position,
+ count: 10,
+ maxCommits: 100,
+ });
+
+ expect(vm.find('.js-graph-bar').classes()).toContain(roundedClass);
+ });
+
+ it.each`
+ count | label
+ ${100} | ${'100'}
+ ${1000} | ${'999+'}
+ `('renders label as $roundedClass for $count', ({ count, label }) => {
+ factory({
+ position: 'left',
+ count,
+ maxCommits: 1000,
+ });
+
+ expect(vm.find('.js-graph-count').text()).toContain(label);
+ });
+
+ it('sets width of bar', () => {
+ factory({
+ position: 'left',
+ count: 100,
+ maxCommits: 1000,
+ });
+
+ expect(vm.find('.js-graph-bar').attributes('style')).toEqual('width: 10%;');
+ });
+});
diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb
index a3511e078ce..ed3e31b3c53 100644
--- a/spec/helpers/appearances_helper_spec.rb
+++ b/spec/helpers/appearances_helper_spec.rb
@@ -8,6 +8,22 @@ describe AppearancesHelper do
allow(helper).to receive(:current_user).and_return(user)
end
+ describe '.current_appearance' do
+ it 'memoizes empty appearance' do
+ expect(Appearance).to receive(:current).once
+
+ 2.times { helper.current_appearance }
+ end
+
+ it 'memoizes custom appearance' do
+ create(:appearance)
+
+ expect(Appearance).to receive(:current).once.and_call_original
+
+ 2.times { helper.current_appearance }
+ end
+ end
+
describe '#header_message' do
it 'returns nil when header message field is not set' do
create(:appearance)
diff --git a/spec/javascripts/groups/components/group_item_spec.js b/spec/javascripts/groups/components/group_item_spec.js
index 4d6d0c895b6..cc88a7ac6c1 100644
--- a/spec/javascripts/groups/components/group_item_spec.js
+++ b/spec/javascripts/groups/components/group_item_spec.js
@@ -156,6 +156,8 @@ describe('GroupItemComponent', () => {
describe('template', () => {
it('should render component template correctly', () => {
+ const visibilityIconEl = vm.$el.querySelector('.item-visibility');
+
expect(vm.$el.getAttribute('id')).toBe('group-55');
expect(vm.$el.classList.contains('group-row')).toBeTruthy();
@@ -173,6 +175,11 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.title')).toBeDefined();
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
+
+ expect(visibilityIconEl).not.toBe(null);
+ expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
+ expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
+
expect(vm.$el.querySelector('.access-type')).toBeDefined();
expect(vm.$el.querySelector('.description')).toBeDefined();
diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js
index 00d6a4817d7..b2441babf3f 100644
--- a/spec/javascripts/groups/components/item_stats_spec.js
+++ b/spec/javascripts/groups/components/item_stats_spec.js
@@ -108,18 +108,6 @@ describe('ItemStatsComponent', () => {
vm.$destroy();
});
- it('renders item visibility icon and tooltip correctly', () => {
- const vm = createComponent();
-
- const visibilityIconEl = vm.$el.querySelector('.item-visibility');
-
- expect(visibilityIconEl).not.toBe(null);
- expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
- expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
-
- vm.$destroy();
- });
-
it('renders start count and last updated information for project item correctly', () => {
const item = Object.assign({}, mockParentGroupItem, {
type: ITEM_TYPE.PROJECT,
diff --git a/spec/javascripts/monitoring/charts/column_spec.js b/spec/javascripts/monitoring/charts/column_spec.js
new file mode 100644
index 00000000000..d8ac68b9484
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/column_spec.js
@@ -0,0 +1,58 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import ColumnChart from '~/monitoring/components/charts/column.vue';
+
+describe('Column component', () => {
+ let columnChart;
+
+ beforeEach(() => {
+ columnChart = shallowMount(ColumnChart, {
+ propsData: {
+ graphData: {
+ queries: [
+ {
+ x_label: 'Time',
+ y_label: 'Usage',
+ result: [
+ {
+ metric: {},
+ values: [
+ [1495700554.925, '8.0390625'],
+ [1495700614.925, '8.0390625'],
+ [1495700674.925, '8.0390625'],
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ containerWidth: 100,
+ },
+ });
+ });
+
+ afterEach(() => {
+ columnChart.destroy();
+ });
+
+ describe('wrapped components', () => {
+ describe('GitLab UI column chart', () => {
+ let glColumnChart;
+
+ beforeEach(() => {
+ glColumnChart = columnChart.find(GlColumnChart);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glColumnChart.isVueInstance()).toBe(true);
+ });
+
+ it('receives data properties needed for proper chart render', () => {
+ const props = glColumnChart.props();
+
+ expect(props.data).toBe(columnChart.vm.chartData);
+ expect(props.option).toBe(columnChart.vm.chartOptions);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 42abb4d83f0..258530f32f7 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -217,7 +217,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
+ expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next ›');
component.$el.querySelector('.js-next-button .page-link').click();
@@ -237,7 +237,7 @@ describe('Pagination component', () => {
change: spy,
});
- expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
+ expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next ›');
component.$el.querySelector('.js-next-button .page-link').click();
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index aa4e358b148..3d57ce431ab 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -141,11 +141,11 @@ describe Gitlab::Ci::Ansi2html do
end
it "replaces newlines with line break tags" do
- expect(convert_html("\n")).to eq('<span class=""></span><br/><span class=""></span>')
+ expect(convert_html("\n")).to eq('<span class=""><br/><span class=""></span></span>')
end
it "groups carriage returns with newlines" do
- expect(convert_html("\r\n")).to eq('<span class=""></span><br/><span class=""></span>')
+ expect(convert_html("\r\n")).to eq('<span class=""><br/><span class=""></span></span>')
end
describe "incremental update" do
@@ -193,7 +193,7 @@ describe Gitlab::Ci::Ansi2html do
let(:pre_text) { "Hello\r" }
let(:pre_html) { "<span class=\"\">Hello\r</span>" }
let(:text) { "\nWorld" }
- let(:html) { "<span class=\"\"></span><br/><span class=\"\">World</span>" }
+ let(:html) { "<span class=\"\"><br/><span class=\"\">World</span></span>" }
it_behaves_like 'stateable converter'
end
@@ -232,7 +232,7 @@ describe Gitlab::Ci::Ansi2html do
it 'prints light red' do
text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
header = %{<span class="term-fg-l-red section js-section-header section-header js-s-#{class_name(section_name)}">Hello</span>}
- line_break = %{<span class="section js-section-header section-header js-s-#{class_name(section_name)}"></span><br/>}
+ line_break = %{<span class="section js-section-header section-header js-s-#{class_name(section_name)}"><br/></span>}
line = %{<span class="section line s_#{class_name(section_name)}"></span>}
empty_line = %{<span class="section js-s-#{class_name(section_name)}"></span>}
html = "#{section_start_html}#{header}#{line_break}#{line}#{empty_line}#{section_end_html}"
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index 5da414dd629..51e16c99688 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -45,12 +45,6 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
it { is_expected.to be_truthy }
end
end
-
- context 'and cluster is project type' do
- let(:cluster) { create(:cluster, :project) }
-
- it { is_expected.to be_falsey }
- end
end
context 'and no cluster to deploy to' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index fae8add6453..7991e2f48b5 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -153,76 +153,72 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
- context 'when keywords and pipeline source policy matches' do
- possibilities = [%w[pushes push],
- %w[web web],
- %w[triggers trigger],
- %w[schedules schedule],
- %w[api api],
- %w[external external]]
-
- context 'when using only' do
- possibilities.each do |keyword, source|
- context "when using keyword `#{keyword}` and source `#{source}`" do
- let(:pipeline) do
- build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
- end
+ context 'with source-keyword policy' do
+ using RSpec::Parameterized
+
+ let(:pipeline) { build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) }
+
+ context 'matches' do
+ where(:keyword, :source) do
+ [
+ %w(pushes push),
+ %w(web web),
+ %w(triggers trigger),
+ %w(schedules schedule),
+ %w(api api),
+ %w(external external)
+ ]
+ end
+ with_them do
+ context 'using an only policy' do
let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
it { is_expected.to be_included }
end
- end
- end
-
- context 'when using except' do
- possibilities.each do |keyword, source|
- context "when using keyword `#{keyword}` and source `#{source}`" do
- let(:pipeline) do
- build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
- end
+ context 'using an except policy' do
let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
it { is_expected.not_to be_included }
end
+
+ context 'using both only and except policies' do
+ let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+
+ it { is_expected.not_to be_included }
+ end
end
end
- end
- context 'when keywords and pipeline source does not match' do
- possibilities = [%w[pushes web],
- %w[web push],
- %w[triggers schedule],
- %w[schedules external],
- %w[api trigger],
- %w[external api]]
-
- context 'when using only' do
- possibilities.each do |keyword, source|
- context "when using keyword `#{keyword}` and source `#{source}`" do
- let(:pipeline) do
- build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
- end
+ context 'non-matches' do
+ where(:keyword, :source) do
+ %w(web trigger schedule api external).map { |source| ['pushes', source] } +
+ %w(push trigger schedule api external).map { |source| ['web', source] } +
+ %w(push web schedule api external).map { |source| ['triggers', source] } +
+ %w(push web trigger api external).map { |source| ['schedules', source] } +
+ %w(push web trigger schedule external).map { |source| ['api', source] } +
+ %w(push web trigger schedule api).map { |source| ['external', source] }
+ end
+ with_them do
+ context 'using an only policy' do
let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
it { is_expected.not_to be_included }
end
- end
- end
-
- context 'when using except' do
- possibilities.each do |keyword, source|
- context "when using keyword `#{keyword}` and source `#{source}`" do
- let(:pipeline) do
- build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
- end
+ context 'using an except policy' do
let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
it { is_expected.to be_included }
end
+
+ context 'using both only and except policies' do
+ let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+
+ it { is_expected.not_to be_included }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 0d03eef99c8..35250632e86 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -65,9 +65,9 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
result = stream.html
expect(result).to eq(
- "<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ</span><br/><span class=\"\"></span>"\
- "<span class=\"term-fg-green\">許功蓋</span><span class=\"\"></span><br/>"\
- "<span class=\"\"></span>")
+ "<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ<br/><span class=\"\"></span></span>"\
+ "<span class=\"term-fg-green\">許功蓋</span><span class=\"\"><br/>"\
+ "<span class=\"\"></span></span>")
expect(result.encoding).to eq(Encoding.default_external)
end
end
@@ -306,8 +306,8 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
shared_examples_for 'htmls' do
it "returns html" do
expect(stream.html).to eq(
- "<span class=\"\">12</span><br/><span class=\"\">34</span><br/>"\
- "<span class=\"\">56</span>")
+ "<span class=\"\">12<br/><span class=\"\">34<br/>"\
+ "<span class=\"\">56</span></span></span>")
end
it "returns html for last line only" do
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index ed9a1e23529..1b5dd2538e0 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -38,9 +38,11 @@ describe Gitlab::DataBuilder::Note do
end
describe 'When asking for a note on issue' do
+ let(:label) { create(:label, project: project) }
+
let(:issue) do
- create(:issue, created_at: fixed_time, updated_at: fixed_time,
- project: project)
+ create(:labeled_issue, created_at: fixed_time, updated_at: fixed_time,
+ project: project, labels: [label])
end
let(:note) do
@@ -48,13 +50,16 @@ describe Gitlab::DataBuilder::Note do
end
it 'returns the note and issue-specific data' do
+ without_timestamps = lambda { |label| label.except('created_at', 'updated_at') }
+ hook_attrs = issue.reload.hook_attrs
+
expect(data).to have_key(:issue)
- expect(data[:issue].except('updated_at'))
- .to eq(issue.reload.hook_attrs.except('updated_at'))
+ expect(data[:issue].except('updated_at', 'labels'))
+ .to eq(hook_attrs.except('updated_at', 'labels'))
expect(data[:issue]['updated_at'])
- .to be >= issue.hook_attrs['updated_at']
- expect(data[:issue]['labels'])
- .to eq(issue.hook_attrs['labels'])
+ .to be >= hook_attrs['updated_at']
+ expect(data[:issue]['labels'].map(&without_timestamps))
+ .to eq(hook_attrs['labels'].map(&without_timestamps))
end
context 'with confidential issue' do
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index f066c0e3813..b06d05c1c7f 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe Gitlab::HookData::IssueBuilder do
- set(:issue) { create(:issue) }
+ set(:label) { create(:label) }
+ set(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
let(:builder) { described_class.new(issue) }
describe '#build' do
@@ -39,6 +40,7 @@ describe Gitlab::HookData::IssueBuilder do
expect(data).to include(:human_time_estimate)
expect(data).to include(:human_total_time_spent)
expect(data).to include(:assignee_ids)
+ expect(data).to include('labels' => [label.hook_attrs])
end
context 'when the issue has an image in the description' do
diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb
index 74157e5c67c..da263bc7523 100644
--- a/spec/lib/gitlab/search/found_blob_spec.rb
+++ b/spec/lib/gitlab/search/found_blob_spec.rb
@@ -3,14 +3,15 @@
require 'spec_helper'
describe Gitlab::Search::FoundBlob do
- describe 'parsing results' do
- let(:project) { create(:project, :public, :repository) }
+ let(:project) { create(:project, :public, :repository) }
+
+ describe 'parsing content results' do
let(:results) { project.repository.search_files_by_content('feature', 'master') }
let(:search_result) { results.first }
subject { described_class.new(content_match: search_result, project: project) }
- it "returns a valid FoundBlob" do
+ it 'returns a valid FoundBlob' do
is_expected.to be_an described_class
expect(subject.id).to be_nil
expect(subject.path).to eq('CHANGELOG')
@@ -21,13 +22,13 @@ describe Gitlab::Search::FoundBlob do
expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
end
- it "doesn't parses content if not needed" do
+ it 'does not parse content if not needed' do
expect(subject).not_to receive(:parse_search_result)
expect(subject.project_id).to eq(project.id)
expect(subject.binary_filename).to eq('CHANGELOG')
end
- it "parses content only once when needed" do
+ it 'parses content only once when needed' do
expect(subject).to receive(:parse_search_result).once.and_call_original
expect(subject.filename).to eq('CHANGELOG')
expect(subject.startline).to eq(188)
@@ -119,7 +120,7 @@ describe Gitlab::Search::FoundBlob do
end
end
- context "when filename has extension" do
+ context 'when filename has extension' do
let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
it { expect(subject.path).to eq('CONTRIBUTE.md') }
@@ -127,7 +128,7 @@ describe Gitlab::Search::FoundBlob do
it { expect(subject.basename).to eq('CONTRIBUTE') }
end
- context "when file under directory" do
+ context 'when file is under directory' do
let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" }
it { expect(subject.path).to eq('a/b/c.md') }
@@ -135,4 +136,28 @@ describe Gitlab::Search::FoundBlob do
it { expect(subject.basename).to eq('a/b/c') }
end
end
+
+ describe 'parsing title results' do
+ context 'when file is under directory' do
+ let(:path) { 'a/b/c.md' }
+
+ subject { described_class.new(blob_filename: path, project: project, ref: 'master') }
+
+ before do
+ allow(Gitlab::Git::Blob).to receive(:batch).and_return([
+ Gitlab::Git::Blob.new(path: path)
+ ])
+ end
+
+ it { expect(subject.path).to eq('a/b/c.md') }
+ it { expect(subject.filename).to eq('a/b/c.md') }
+ it { expect(subject.basename).to eq('a/b/c') }
+
+ context 'when filename has multiple extensions' do
+ let(:path) { 'a/b/c.whatever.md' }
+
+ it { expect(subject.basename).to eq('a/b/c.whatever') }
+ end
+ end
+ end
end
diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index 2f594dbf9d1..eae83cd64d3 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -9,7 +9,7 @@ describe Emails::PagesDomains do
set(:user) { project.creator }
shared_examples 'a pages domain email' do
- let(:test_recipient) { user }
+ let(:recipient) { user }
it_behaves_like 'an email sent to a user'
it_behaves_like 'an email sent from GitLab'
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 11af6837dab..fa1343fe759 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -45,7 +45,7 @@ describe Notify do
context 'for a project' do
shared_examples 'an assignee email' do
- let(:test_recipient) { assignee }
+ let(:recipient) { assignee }
it_behaves_like 'an email sent to a user'
@@ -55,7 +55,7 @@ describe Notify do
aggregate_failures do
expect(sender.display_name).to eq(current_user.name)
expect(sender.address).to eq(gitlab_sender)
- expect(subject).to deliver_to(assignee.email)
+ expect(subject).to deliver_to(recipient.notification_email)
end
end
end
@@ -547,12 +547,13 @@ describe Notify do
let(:host) { Gitlab.config.gitlab.host }
context 'in discussion' do
- set(:first_note) { create(:discussion_note_on_issue) }
- set(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note) }
- set(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note) }
+ set(:first_note) { create(:discussion_note_on_issue, project: project) }
+ set(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note, project: project) }
+ set(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note, project: project) }
subject { described_class.note_issue_email(recipient.id, third_note.id) }
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
@@ -572,10 +573,11 @@ describe Notify do
end
context 'individual issue comments' do
- set(:note) { create(:note_on_issue) }
+ set(:note) { create(:note_on_issue, project: project) }
subject { described_class.note_issue_email(recipient.id, note.id) }
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'appearance header and footer enabled'
it_behaves_like 'appearance header and footer not enabled'
@@ -604,13 +606,13 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has the correct subject and body' do
- is_expected.to have_referable_subject(project_snippet, reply: true)
+ is_expected.to have_referable_subject(project_snippet, include_group: true, reply: true)
is_expected.to have_body_text project_snippet_note.note
end
end
describe 'project was moved' do
- let(:test_recipient) { user }
+ let(:recipient) { user }
subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
it_behaves_like 'an email sent to a user'
@@ -811,7 +813,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_subject("Re: #{project.name} | #{commit.title} (#{commit.short_id})")
+ is_expected.to have_subject("Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})")
is_expected.to have_body_text(commit.short_id)
end
end
@@ -837,7 +839,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_referable_subject(merge_request, reply: true)
+ is_expected.to have_referable_subject(merge_request, include_group: true, reply: true)
is_expected.to have_body_text note_on_merge_request_path
end
end
@@ -863,7 +865,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_referable_subject(issue, include_group: true, reply: true)
is_expected.to have_body_text(note_on_issue_path)
end
end
@@ -929,7 +931,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})"
+ is_expected.to have_subject "Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})"
end
it 'contains a link to the commit' do
@@ -957,7 +959,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request, reply: true)
+ is_expected.to have_referable_subject(merge_request, include_group: true, reply: true)
end
it 'contains a link to the merge request note' do
@@ -985,7 +987,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, reply: true)
+ is_expected.to have_referable_subject(issue, include_group: true, reply: true)
end
it 'contains a link to the issue note' do
diff --git a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
new file mode 100644
index 00000000000..3c880c6f5fd
--- /dev/null
+++ b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190620112608_enqueue_reset_merge_status_second_run.rb')
+
+describe EnqueueResetMergeStatusSecondRun, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id, extra_params = {})
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }.merge(extra_params)
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ create_merge_request(1, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(2, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(3, state: 'opened', merge_status: 'can_be_merged')
+ create_merge_request(4, state: 'merged', merge_status: 'can_be_merged')
+ create_merge_request(5, state: 'opened', merge_status: 'unchecked')
+
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(5.minutes, 1, 2)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(10.minutes, 3, 4)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(15.minutes, 5, 5)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(3)
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index d7b81caddf5..aee43025288 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -88,23 +88,8 @@ describe Ci::PipelineSchedule do
describe '#set_next_run_at' do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
- let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) }
-
- let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
- .next_time_from(ideal_next_run_at)
- end
-
- let(:cron_worker_next_run_at) do
- Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
- .next_time_from(Time.zone.now)
- end
-
- context 'when creates new pipeline schedule' do
- it 'updates next_run_at automatically' do
- expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
- end
- end
+ let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_from, Time.zone.now) }
+ let(:cron_worker_next_run_at) { pipeline_schedule.send(:cron_worker_next_run_from, Time.zone.now) }
context 'when PipelineScheduleWorker runs at a specific interval' do
before do
@@ -129,7 +114,7 @@ describe Ci::PipelineSchedule do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) }
it "updates next_run_at to the sidekiq worker's execution time" do
- Timecop.freeze(2019, 06, 19, 12, 00) do
+ Timecop.freeze(Time.parse("2019-06-01 12:18:00+0000")) do
expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at)
end
end
@@ -157,9 +142,8 @@ describe Ci::PipelineSchedule do
let(:new_cron) { '0 0 1 1 *' }
it 'updates next_run_at automatically' do
- pipeline_schedule.update!(cron: new_cron)
-
- expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
+ expect { pipeline_schedule.update!(cron: new_cron) }
+ .to change { pipeline_schedule.next_run_at }
end
end
end
@@ -167,21 +151,24 @@ describe Ci::PipelineSchedule do
describe '#schedule_next_run!' do
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
- context 'when reschedules after 10 days from now' do
- let(:future_time) { 10.days.from_now }
- let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) }
+ before do
+ pipeline_schedule.update_column(:next_run_at, nil)
+ end
- let(:expected_next_run_at) do
- Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name)
- .next_time_from(ideal_next_run_at)
+ it 'updates next_run_at' do
+ expect { pipeline_schedule.schedule_next_run! }
+ .to change { pipeline_schedule.next_run_at }
+ end
+
+ context 'when record is invalid' do
+ before do
+ allow(pipeline_schedule).to receive(:save!) { raise ActiveRecord::RecordInvalid.new(pipeline_schedule) }
end
- it 'points to proper next_run_at' do
- Timecop.freeze(future_time) do
- pipeline_schedule.schedule_next_run!
+ it 'nullifies the next run at' do
+ pipeline_schedule.schedule_next_run!
- expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at)
- end
+ expect(pipeline_schedule.next_run_at).to be_nil
end
end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 0fa5d031736..05b3035e591 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -484,27 +484,4 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to include(pods: []) }
end
end
-
- describe '#update_kubernetes_namespace' do
- let(:cluster) { create(:cluster, :provided_by_gcp) }
- let(:platform) { cluster.platform }
-
- context 'when namespace is updated' do
- it 'calls ConfigureWorker' do
- expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once
-
- platform.namespace = 'new-namespace'
- platform.save
- end
- end
-
- context 'when namespace is not updated' do
- it 'does not call ConfigureWorker' do
- expect(ClusterConfigureWorker).not_to receive(:perform_async)
-
- platform.username = "new-username"
- platform.save
- end
- end
- end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index a5c7e9db2a1..d5b016dc8f6 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -862,4 +862,13 @@ describe Issue do
end
end
end
+
+ describe "#labels_hook_attrs" do
+ let(:label) { create(:label) }
+ let(:issue) { create(:labeled_issue, labels: [label]) }
+
+ it "returns a list of label hook attributes" do
+ expect(issue.labels_hook_attrs).to eq([label.hook_attrs])
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index c6251326c22..fc28c216b21 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1996,57 +1996,6 @@ describe MergeRequest do
end
end
- describe '#check_if_can_be_merged' do
- let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
-
- shared_examples 'checking if can be merged' do
- context 'when it is not broken and has no conflicts' do
- before do
- allow(subject).to receive(:broken?) { false }
- allow(project.repository).to receive(:can_be_merged?).and_return(true)
- end
-
- it 'is marked as mergeable' do
- expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged')
- end
- end
-
- context 'when broken' do
- before do
- allow(subject).to receive(:broken?) { true }
- allow(project.repository).to receive(:can_be_merged?).and_return(false)
- end
-
- it 'becomes unmergeable' do
- expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
- end
- end
-
- context 'when it has conflicts' do
- before do
- allow(subject).to receive(:broken?) { false }
- allow(project.repository).to receive(:can_be_merged?).and_return(false)
- end
-
- it 'becomes unmergeable' do
- expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
- end
- end
- end
-
- context 'when merge_status is unchecked' do
- subject { create(:merge_request, source_project: project, merge_status: :unchecked) }
-
- it_behaves_like 'checking if can be merged'
- end
-
- context 'when merge_status is unchecked' do
- subject { create(:merge_request, source_project: project, merge_status: :cannot_be_merged_recheck) }
-
- it_behaves_like 'checking if can be merged'
- end
- end
-
describe '#mergeable?' do
let(:project) { create(:project) }
@@ -2060,7 +2009,7 @@ describe MergeRequest do
it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do
allow(subject).to receive(:mergeable_state?) { true }
- expect(subject).to receive(:check_if_can_be_merged)
+ expect(subject).to receive(:check_mergeability)
expect(subject).to receive(:can_be_merged?) { true }
expect(subject.mergeable?).to be_truthy
@@ -2074,7 +2023,7 @@ describe MergeRequest do
it 'checks if merge request can be merged' do
allow(subject).to receive(:mergeable_ci_state?) { true }
- expect(subject).to receive(:check_if_can_be_merged)
+ expect(subject).to receive(:check_mergeability)
subject.mergeable?
end
@@ -3142,38 +3091,6 @@ describe MergeRequest do
end
end
- describe '#mergeable_to_ref?' do
- it 'returns true when merge request is mergeable' do
- subject = create(:merge_request)
-
- expect(subject.mergeable_to_ref?).to be(true)
- end
-
- it 'returns false when merge request is already merged' do
- subject = create(:merge_request, :merged)
-
- expect(subject.mergeable_to_ref?).to be(false)
- end
-
- it 'returns false when merge request is closed' do
- subject = create(:merge_request, :closed)
-
- expect(subject.mergeable_to_ref?).to be(false)
- end
-
- it 'returns false when merge request is work in progress' do
- subject = create(:merge_request, title: 'WIP: The feature')
-
- expect(subject.mergeable_to_ref?).to be(false)
- end
-
- it 'returns false when merge request has no commits' do
- subject = create(:merge_request, source_branch: 'empty-branch', target_branch: 'master')
-
- expect(subject.mergeable_to_ref?).to be(false)
- end
- end
-
describe '#merge_participants' do
it 'contains author' do
expect(subject.merge_participants).to eq([subject.author])
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 4fb7b71a3c7..661957cf08b 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -479,4 +479,30 @@ describe PagesDomain do
end
end
end
+
+ describe '.need_auto_ssl_renewal' do
+ subject { described_class.need_auto_ssl_renewal }
+
+ let!(:domain_with_user_provided_certificate) { create(:pages_domain) }
+ let!(:domain_with_expired_user_provided_certificate) do
+ create(:pages_domain, :with_expired_certificate)
+ end
+ let!(:domain_with_user_provided_certificate_and_auto_ssl) do
+ create(:pages_domain, auto_ssl_enabled: true)
+ end
+
+ let!(:domain_with_gitlab_provided_certificate) { create(:pages_domain, :letsencrypt) }
+ let!(:domain_with_expired_gitlab_provided_certificate) do
+ create(:pages_domain, :letsencrypt, :with_expired_certificate)
+ end
+
+ it 'contains only domains needing verification' do
+ is_expected.to(
+ contain_exactly(
+ domain_with_user_provided_certificate_and_auto_ssl,
+ domain_with_expired_gitlab_provided_certificate
+ )
+ )
+ end
+ end
end
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 1c434b25205..3d875bc49e7 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -7,10 +7,11 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
include StubRequests
let(:teamcity_url) { 'http://gitlab.com/teamcity' }
+ let(:project) { create(:project) }
subject(:service) do
described_class.create(
- project: create(:project),
+ project: project,
properties: {
teamcity_url: teamcity_url,
username: 'mic',
@@ -207,6 +208,97 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
end
end
+ describe '#execute' do
+ context 'when push' do
+ let(:data) do
+ {
+ object_kind: 'push',
+ ref: 'refs/heads/dev-123_branch',
+ after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
+ total_commits_count: 1
+ }
+ end
+
+ it 'handles push request correctly' do
+ stub_post_to_build_queue(branch: 'dev-123_branch')
+
+ expect(service.execute(data)).to include('Ok')
+ end
+
+ it 'returns nil when ref is blank' do
+ data[:after] = Gitlab::Git::BLANK_SHA
+
+ expect(service.execute(data)).to be_nil
+ end
+
+ it 'returns nil when there is no content' do
+ data[:total_commits_count] = 0
+
+ expect(service.execute(data)).to be_nil
+ end
+
+ it 'returns nil when a merge request is opened for the same ref' do
+ create(:merge_request, source_project: project, source_branch: 'dev-123_branch')
+
+ expect(service.execute(data)).to be_nil
+ end
+ end
+
+ context 'when merge_request' do
+ let(:data) do
+ {
+ object_kind: 'merge_request',
+ ref: 'refs/heads/dev-123_branch',
+ after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
+ total_commits_count: 1,
+ object_attributes: {
+ state: 'opened',
+ source_branch: 'dev-123_branch',
+ merge_status: 'unchecked'
+ }
+ }
+ end
+
+ it 'handles merge request correctly' do
+ stub_post_to_build_queue(branch: 'dev-123_branch')
+
+ expect(service.execute(data)).to include('Ok')
+ end
+
+ it 'returns nil when merge request is not opened' do
+ data[:object_attributes][:state] = 'closed'
+
+ expect(service.execute(data)).to be_nil
+ end
+
+ it 'returns nil unless merge request is marked as unchecked' do
+ data[:object_attributes][:merge_status] = 'can_be_merged'
+
+ expect(service.execute(data)).to be_nil
+ end
+ end
+
+ it 'returns nil when event is not supported' do
+ data = { object_kind: 'foo' }
+
+ expect(service.execute(data)).to be_nil
+ end
+ end
+
+ def stub_post_to_build_queue(branch:)
+ teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/buildQueue'
+ body ||= %Q(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>)
+ auth = %w(mic password)
+
+ stub_full_request(teamcity_full_url, method: :post).with(
+ basic_auth: auth,
+ body: body,
+ headers: {
+ 'Content-Type' => 'application/xml'
+ }
+ ).to_return(status: 200, body: 'Ok', headers: {})
+ end
+
def stub_request(status: 200, body: nil, build_status: 'success')
teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123'
auth = %w(mic password)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 8b503777443..f9c8b42afa8 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -65,7 +65,7 @@ describe API::Branches do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
end
end
@@ -175,7 +175,7 @@ describe API::Branches do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
end
end
@@ -337,7 +337,7 @@ describe API::Branches do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { put api(route, current_user) }
end
end
@@ -471,7 +471,7 @@ describe API::Branches do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { put api(route, current_user) }
end
end
@@ -547,7 +547,7 @@ describe API::Branches do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { post api(route, current_user) }
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index f104da6ebba..3df5d9412f8 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -736,7 +736,7 @@ describe API::Commits do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
end
end
@@ -825,7 +825,7 @@ describe API::Commits do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
end
end
@@ -968,7 +968,7 @@ describe API::Commits do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
end
end
@@ -1067,7 +1067,7 @@ describe API::Commits do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
end
end
@@ -1169,7 +1169,7 @@ describe API::Commits do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { post api(route, current_user), params: { branch: 'master' } }
end
end
@@ -1324,7 +1324,7 @@ describe API::Commits do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { post api(route, current_user), params: { branch: branch } }
end
end
@@ -1435,7 +1435,7 @@ describe API::Commits do
context 'when repository is disabled' do
include_context 'disabled repository'
- it_behaves_like '403 response' do
+ it_behaves_like '404 response' do
let(:request) { post api(route, current_user), params: { note: 'My comment' } }
end
end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 8fc7fdc8632..745f3c55ac8 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -34,6 +34,47 @@ describe API::Environments do
expect(json_response.first['project'].keys).to contain_exactly(*project_data_keys)
expect(json_response.first).not_to have_key("last_deployment")
end
+
+ context 'when filtering' do
+ let!(:environment2) { create(:environment, project: project) }
+
+ it 'returns environment by name' do
+ get api("/projects/#{project.id}/environments?name=#{environment.name}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['name']).to eq(environment.name)
+ end
+
+ it 'returns no environment by non-existent name' do
+ get api("/projects/#{project.id}/environments?name=test", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(0)
+ end
+
+ it 'returns environments by name_like' do
+ get api("/projects/#{project.id}/environments?search=envir", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ end
+
+ it 'returns no environment by non-existent name_like' do
+ get api("/projects/#{project.id}/environments?search=test", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(0)
+ end
+ end
end
context 'as non member' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 9f9180bc8c9..76d093e0774 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -1546,52 +1546,80 @@ describe API::MergeRequests do
end
end
- describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref" do
- let(:pipeline) { create(:ci_pipeline_without_jobs) }
+ describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref" do
+ before do
+ merge_request.mark_as_unchecked!
+ end
+
+ let(:merge_request_iid) { merge_request.iid }
+
let(:url) do
- "/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge_to_ref"
+ "/projects/#{project.id}/merge_requests/#{merge_request_iid}/merge_ref"
end
it 'returns the generated ID from the merge service in case of success' do
- put api(url, user), params: { merge_commit_message: 'Custom message' }
-
- commit = project.commit(json_response['commit_id'])
+ get api(url, user)
expect(response).to have_gitlab_http_status(200)
- expect(json_response['commit_id']).to be_present
- expect(commit.message).to eq('Custom message')
+ expect(json_response['commit_id']).to eq(merge_request.merge_ref_head.sha)
end
- it "returns 400 if branch can't be merged" do
- merge_request.update!(state: 'merged')
+ context 'when merge-ref is not synced with merge status' do
+ before do
+ merge_request.update!(merge_status: 'cannot_be_merged')
+ end
- put api(url, user)
+ it 'returns 200 if MR can be merged' do
+ get api(url, user)
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message'])
- .to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}")
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commit_id']).to eq(merge_request.merge_ref_head.sha)
+ end
+
+ it 'returns 400 if MR cannot be merged' do
+ expect_next_instance_of(MergeRequests::MergeToRefService) do |merge_request|
+ expect(merge_request).to receive(:execute) { { status: :failed } }
+ end
+
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq('Merge request is not mergeable')
+ end
end
- it 'returns 403 if user has no permissions to merge to the ref' do
- user2 = create(:user)
- project.add_reporter(user2)
+ context 'when user has no access to the MR' do
+ let(:project) { create(:project, :private) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- put api(url, user2)
+ it 'returns 404' do
+ project.add_guest(user)
- expect(response).to have_gitlab_http_status(403)
- expect(json_response['message']).to eq('403 Forbidden')
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
end
- it 'returns 404 for an invalid merge request IID' do
- put api("/projects/#{project.id}/merge_requests/12345/merge_to_ref", user)
+ context 'when invalid merge request IID' do
+ let(:merge_request_iid) { '12345' }
- expect(response).to have_gitlab_http_status(404)
+ it 'returns 404' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
end
- it "returns 404 if the merge request id is used instead of iid" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
+ context 'when merge request ID is used instead IID' do
+ let(:merge_request_iid) { merge_request.id }
- expect(response).to have_gitlab_http_status(404)
+ it 'returns 404' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
end
end
diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
index 2664649df47..5f91acb8e84 100644
--- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
@@ -19,10 +19,6 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do
subject { described_class.new.execute(provider) }
- before do
- allow(ClusterConfigureWorker).to receive(:perform_async)
- end
-
shared_examples 'success' do
it 'configures provider and kubernetes' do
subject
@@ -42,12 +38,6 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do
expect(platform.password).to eq(password)
expect(platform.token).to eq(token)
end
-
- it 'calls ClusterConfigureWorker in a ascync fashion' do
- expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id)
-
- subject
- end
end
shared_examples 'error' do
diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb
index 21b37f88fd8..3ee45375dca 100644
--- a/spec/services/clusters/update_service_spec.rb
+++ b/spec/services/clusters/update_service_spec.rb
@@ -39,7 +39,6 @@ describe Clusters::UpdateService do
end
before do
- allow(ClusterConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 0ac23050caf..61f99f82a76 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -72,10 +72,6 @@ describe MergeRequests::MergeToRefService do
let(:merge_request) { create(:merge_request, :simple) }
let(:project) { merge_request.project }
- before do
- project.add_maintainer(user)
- end
-
describe '#execute' do
let(:service) do
described_class.new(project, user, commit_message: 'Awesome message',
@@ -92,6 +88,12 @@ describe MergeRequests::MergeToRefService do
it_behaves_like 'successfully evaluates pre-condition checks'
context 'commit history comparison with regular MergeService' do
+ before do
+ # The merge service needs an authorized user while merge-to-ref
+ # doesn't.
+ project.add_maintainer(user)
+ end
+
let(:merge_ref_service) do
described_class.new(project, user, {})
end
@@ -104,12 +106,18 @@ describe MergeRequests::MergeToRefService do
it_behaves_like 'MergeService for target ref'
end
- context 'when merge commit with squash', :quarantine do
+ context 'when merge commit with squash' do
before do
- merge_request.update!(squash: true, source_branch: 'master', target_branch: 'feature')
+ merge_request.update!(squash: true)
end
it_behaves_like 'MergeService for target ref'
+
+ it 'does not squash before merging' do
+ expect(MergeRequests::SquashService).not_to receive(:new)
+
+ process_merge_to_ref
+ end
end
end
@@ -136,9 +144,9 @@ describe MergeRequests::MergeToRefService do
let(:merge_method) { :merge }
it 'returns error' do
- allow(merge_request).to receive(:mergeable_to_ref?) { false }
+ allow(project).to receive_message_chain(:repository, :merge_to_ref) { nil }
- error_message = "Merge request is not mergeable to #{merge_request.merge_ref_path}"
+ error_message = 'Conflicts detected during merge'
result = service.execute(merge_request)
@@ -170,28 +178,5 @@ describe MergeRequests::MergeToRefService do
it { expect(todo).not_to be_done }
end
-
- context 'when merge request is WIP state' do
- it 'fails to merge' do
- merge_request = create(:merge_request, title: 'WIP: The feature')
-
- result = service.execute(merge_request)
-
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}")
- end
- end
-
- it 'returns error when user has no authorization to admin the merge request' do
- unauthorized_user = create(:user)
- project.add_reporter(unauthorized_user)
-
- service = described_class.new(project, unauthorized_user)
-
- result = service.execute(merge_request)
-
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq('You are not allowed to merge to this ref')
- end
end
end
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
new file mode 100644
index 00000000000..6efece64092
--- /dev/null
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -0,0 +1,285 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::MergeabilityCheckService do
+ shared_examples_for 'unmergeable merge request' do
+ it 'updates or keeps merge status as cannot_be_merged' do
+ subject
+
+ expect(merge_request.merge_status).to eq('cannot_be_merged')
+ end
+
+ it 'does not change the merge ref HEAD' do
+ expect { subject }.not_to change(merge_request, :merge_ref_head)
+ end
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result).to be_error
+ end
+ end
+
+ shared_examples_for 'mergeable merge request' do
+ it 'updates or keeps merge status as can_be_merged' do
+ subject
+
+ expect(merge_request.merge_status).to eq('can_be_merged')
+ end
+
+ it 'updates the merge ref' do
+ expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
+ end
+
+ it 'returns ServiceResponse.success' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result).to be_success
+ end
+
+ it 'ServiceResponse has merge_ref_head payload' do
+ result = subject
+
+ expect(result.payload.keys).to contain_exactly(:merge_ref_head)
+ expect(result.payload[:merge_ref_head].keys)
+ .to contain_exactly(:commit_id, :target_id, :source_id)
+ end
+ end
+
+ describe '#execute' do
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, merge_status: :unchecked, source_project: project, target_project: project) }
+ let(:repo) { project.repository }
+
+ subject { described_class.new(merge_request).execute }
+
+ before do
+ project.add_developer(merge_request.author)
+ end
+
+ it_behaves_like 'mergeable merge request'
+
+ context 'when multiple calls to the service' do
+ it 'returns success' do
+ subject
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.success?).to be(true)
+ end
+
+ it 'second call does not change the merge-ref' do
+ expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
+ expect { subject }.not_to change(merge_request, :merge_ref_head)
+ end
+ end
+
+ context 'disabled merge ref sync feature flag' do
+ before do
+ stub_feature_flags(merge_ref_auto_sync: false)
+ end
+
+ it 'returns error and no payload' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge ref is outdated due to disabled feature')
+ expect(result.payload).to be_empty
+ end
+
+ it 'ignores merge-ref and updates merge status' do
+ expect { subject }.to change(merge_request, :merge_status).from('unchecked').to('can_be_merged')
+ end
+ end
+
+ context 'when broken' do
+ before do
+ allow(merge_request).to receive(:broken?) { true }
+ allow(project.repository).to receive(:can_be_merged?) { false }
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when it has conflicts' do
+ before do
+ allow(merge_request).to receive(:broken?) { false }
+ allow(project.repository).to receive(:can_be_merged?) { false }
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when MR cannot be merged and has no merge ref' do
+ before do
+ merge_request.mark_as_unmergeable!
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when MR cannot be merged and has outdated merge ref' do
+ before do
+ MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ merge_request.mark_as_unmergeable!
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'when merge request is not given' do
+ subject { described_class.new(nil).execute }
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.message).to eq('Invalid argument')
+ end
+ end
+
+ context 'when read only DB' do
+ it 'returns ServiceResponse.error' do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.message).to eq('Unsupported operation')
+ end
+ end
+
+ context 'when fails to update the merge-ref' do
+ before do
+ expect_next_instance_of(MergeRequests::MergeToRefService) do |merge_to_ref|
+ expect(merge_to_ref).to receive(:execute).and_return(status: :failed)
+ end
+ end
+
+ it_behaves_like 'unmergeable merge request'
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge request is not mergeable')
+ end
+ end
+
+ context 'recheck enforced' do
+ subject { described_class.new(merge_request).execute(recheck: true) }
+
+ context 'when MR is mergeable and merge-ref auto-sync is disabled' do
+ before do
+ stub_feature_flags(merge_ref_auto_sync: false)
+ merge_request.mark_as_mergeable!
+ end
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge ref is outdated due to disabled feature')
+ expect(result.payload).to be_empty
+ end
+
+ it 'merge status is not changed' do
+ subject
+
+ expect(merge_request.merge_status).to eq('can_be_merged')
+ end
+ end
+
+ context 'when MR is marked as mergeable, but repo is not mergeable and MR is not opened' do
+ before do
+ # Making sure that we don't touch the merge-status after
+ # the MR is not opened any longer. Source branch might
+ # have been removed, etc.
+ allow(merge_request).to receive(:broken?) { true }
+ merge_request.mark_as_mergeable!
+ merge_request.close!
+ end
+
+ it 'returns ServiceResponse.error' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to eq('Merge ref cannot be updated')
+ expect(result.payload).to be_empty
+ end
+
+ it 'does not change the merge status' do
+ expect { subject }.not_to change(merge_request, :merge_status).from('can_be_merged')
+ end
+ end
+
+ context 'when MR is mergeable but merge-ref does not exists' do
+ before do
+ merge_request.mark_as_mergeable!
+ end
+
+ it_behaves_like 'mergeable merge request'
+ end
+
+ context 'when MR is mergeable but merge-ref is already updated' do
+ before do
+ MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ merge_request.mark_as_mergeable!
+ end
+
+ it 'returns ServiceResponse.success' do
+ result = subject
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result).to be_success
+ expect(result.payload[:merge_ref_head]).to be_present
+ end
+
+ it 'does not recreate the merge-ref' do
+ expect(MergeRequests::MergeToRefService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb
index 30bd4d6820b..e790d272e61 100644
--- a/spec/services/service_response_spec.rb
+++ b/spec/services/service_response_spec.rb
@@ -16,6 +16,13 @@ describe ServiceResponse do
expect(response).to be_success
expect(response.message).to eq('Good orange')
end
+
+ it 'creates a successful response with payload' do
+ response = described_class.success(payload: { good: 'orange' })
+
+ expect(response).to be_success
+ expect(response.payload).to eq(good: 'orange')
+ end
end
describe '.error' do
@@ -33,6 +40,15 @@ describe ServiceResponse do
expect(response.message).to eq('Bad apple')
expect(response.http_status).to eq(400)
end
+
+ it 'creates a failed response with payload' do
+ response = described_class.error(message: 'Bad apple',
+ payload: { bad: 'apple' })
+
+ expect(response).to be_error
+ expect(response.message).to eq('Bad apple')
+ expect(response.payload).to eq(bad: 'apple')
+ end
end
describe '#success?' do
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index ed049daba80..a7175491fa0 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -37,8 +37,19 @@ module EmailHelpers
ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) }
end
- def have_referable_subject(referable, include_project: true, reply: false)
- prefix = (include_project && referable.project ? "#{referable.project.name} | " : '').freeze
+ def have_referable_subject(referable, include_project: true, include_group: false, reply: false)
+ context = []
+
+ context << referable.project.name if include_project && referable.project
+ context << referable.project.group.name if include_group && referable.project.group
+
+ prefix =
+ if context.any?
+ context.join(' | ') + ' | '
+ else
+ ''
+ end
+
prefix = "Re: #{prefix}" if reply
suffix = "#{referable.title} (#{referable.to_reference})"
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index 7993b2870e5..f985b2dcbba 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -5,7 +5,7 @@ shared_examples_for 'common trace features' do
end
it "returns formatted html" do
- expect(trace.html).to eq("<span class=\"\">12</span><br/><span class=\"\">34</span>")
+ expect(trace.html).to eq("<span class=\"\">12<br/><span class=\"\">34</span></span>")
end
it "returns last line of formatted html" do
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index 6894a63ce42..e64c7e37a0c 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -45,18 +45,18 @@ shared_examples 'an email sent to a user' do
let(:group_notification_email) { 'user+group@example.com' }
it 'is sent to user\'s global notification email address' do
- expect(subject).to deliver_to(test_recipient.notification_email)
+ expect(subject).to deliver_to(recipient.notification_email)
end
context 'that is part of a project\'s group' do
it 'is sent to user\'s group notification email address when set' do
- create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email)
+ create(:notification_setting, user: recipient, source: project.group, notification_email: group_notification_email)
expect(subject).to deliver_to(group_notification_email)
end
it 'is sent to user\'s global notification email address when no group email set' do
- create(:notification_setting, user: test_recipient, source: project.group, notification_email: '')
- expect(subject).to deliver_to(test_recipient.notification_email)
+ create(:notification_setting, user: recipient, source: project.group, notification_email: '')
+ expect(subject).to deliver_to(recipient.notification_email)
end
end
@@ -67,17 +67,17 @@ shared_examples 'an email sent to a user' do
it 'is sent to user\'s subgroup notification email address when set' do
# Set top-level group notification email address to make sure it doesn't get selected
- create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email)
+ create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email)
subgroup_notification_email = 'user+subgroup@example.com'
- create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email)
+ create(:notification_setting, user: recipient, source: subgroup, notification_email: subgroup_notification_email)
expect(subject).to deliver_to(subgroup_notification_email)
end
it 'is sent to user\'s group notification email address when set and subgroup email address not set' do
- create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '')
- expect(subject).to deliver_to(test_recipient.notification_email)
+ create(:notification_setting, user: recipient, source: subgroup, notification_email: '')
+ expect(subject).to deliver_to(recipient.notification_email)
end
end
end
diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb
index ffe8796ded9..f4ff8cb15f8 100644
--- a/spec/workers/build_success_worker_spec.rb
+++ b/spec/workers/build_success_worker_spec.rb
@@ -6,51 +6,7 @@ describe BuildSuccessWorker do
describe '#perform' do
subject { described_class.new.perform(build.id) }
- before do
- allow_any_instance_of(Deployment).to receive(:create_ref)
- end
-
context 'when build exists' do
- context 'when deployment was not created with the build creation' do # An edge case during the transition period
- let!(:build) { create(:ci_build, :deploy_to_production) }
-
- before do
- allow(Deployments::FinishedWorker).to receive(:perform_async)
- Deployment.delete_all
- build.reload
- end
-
- it 'creates a successful deployment' do
- expect(build).not_to be_has_deployment
-
- subject
-
- build.reload
- expect(build).to be_has_deployment
- expect(build.deployment).to be_success
- end
- end
-
- context 'when deployment was created with the build creation' do # Counter part of the above edge case
- let!(:build) { create(:ci_build, :deploy_to_production) }
-
- it 'does not create a new deployment' do
- expect(build).to be_has_deployment
-
- expect { subject }.not_to change { Deployment.count }
- end
- end
-
- context 'when build is not associated with project' do
- let!(:build) { create(:ci_build, project: nil) }
-
- it 'does not create deployment' do
- subject
-
- expect(build.reload).not_to be_has_deployment
- end
- end
-
context 'when the build will stop an environment' do
let!(:build) { create(:ci_build, :stop_review_app, environment: environment.name, project: environment.project) }
let(:environment) { create(:environment, state: :available) }
diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb
index 9cc2ad12bfc..3f69962f25d 100644
--- a/spec/workers/cluster_provision_worker_spec.rb
+++ b/spec/workers/cluster_provision_worker_spec.rb
@@ -23,18 +23,11 @@ describe ClusterProvisionWorker do
described_class.new.perform(cluster.id)
end
-
- it 'configures kubernetes platform' do
- expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id)
-
- described_class.new.perform(cluster.id)
- end
end
context 'when cluster does not exist' do
it 'does not provision a cluster' do
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
- expect(ClusterConfigureWorker).not_to receive(:perform_async)
described_class.new.perform(123)
end
diff --git a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
new file mode 100644
index 00000000000..2ae4872f51d
--- /dev/null
+++ b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomainSslRenewalCronWorker do
+ include LetsEncryptHelpers
+
+ subject(:worker) { described_class.new }
+
+ before do
+ stub_lets_encrypt_settings
+ end
+
+ describe '#perform' do
+ let!(:domain) { create(:pages_domain) }
+ let!(:domain_with_enabled_auto_ssl) { create(:pages_domain, auto_ssl_enabled: true) }
+ let!(:domain_with_obtained_letsencrypt) { create(:pages_domain, :letsencrypt, auto_ssl_enabled: true) }
+ let!(:domain_without_auto_certificate) do
+ create(:pages_domain, :without_certificate, :without_key, auto_ssl_enabled: true)
+ end
+
+ let!(:domain_with_expired_auto_ssl) do
+ create(:pages_domain, :letsencrypt, :with_expired_certificate)
+ end
+
+ it 'enqueues a PagesDomainSslRenewalWorker for domains needing renewal' do
+ [domain_without_auto_certificate,
+ domain_with_enabled_auto_ssl,
+ domain_with_expired_auto_ssl].each do |domain|
+ expect(PagesDomainSslRenewalWorker).to receive(:perform_async).with(domain.id)
+ end
+
+ [domain,
+ domain_with_obtained_letsencrypt].each do |domain|
+ expect(PagesDomainVerificationWorker).not_to receive(:perform_async).with(domain.id)
+ end
+
+ worker.perform
+ end
+
+ shared_examples 'does nothing' do
+ it 'does nothing' do
+ expect(PagesDomainSslRenewalWorker).not_to receive(:perform_async)
+
+ worker.perform
+ end
+ end
+
+ context 'when letsencrypt integration is disabled' do
+ before do
+ stub_application_setting(
+ lets_encrypt_terms_of_service_accepted: false
+ )
+ end
+
+ include_examples 'does nothing'
+ end
+ end
+end
diff --git a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
new file mode 100644
index 00000000000..a3d33de1b40
--- /dev/null
+++ b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PagesDomainSslRenewalWorker do
+ include LetsEncryptHelpers
+
+ subject(:worker) { described_class.new }
+
+ let(:domain) { create(:pages_domain) }
+
+ before do
+ stub_lets_encrypt_settings
+ end
+
+ describe '#perform' do
+ it 'delegates to ObtainLetsEncryptCertificateService' do
+ service = double(:service)
+ expect(::PagesDomains::ObtainLetsEncryptCertificateService).to receive(:new).with(domain).and_return(service)
+ expect(service).to receive(:execute)
+
+ worker.perform(domain.id)
+ end
+
+ context 'when domain was deleted' do
+ before do
+ domain.destroy!
+ end
+
+ it 'does nothing' do
+ expect(::PagesDomains::ObtainLetsEncryptCertificateService).not_to receive(:new)
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 5b725f07c93..c54e7cd460b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4829,10 +4829,10 @@ fsevents@^1.2.2, fsevents@^1.2.7:
nan "^2.12.1"
node-pre-gyp "^0.12.0"
-fstream@^1.0.0, fstream@^1.0.2:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
- integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=
+fstream@^1.0.0, fstream@^1.0.12:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
+ integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
dependencies:
graceful-fs "^4.1.2"
inherits "~2.0.0"
@@ -6566,10 +6566,10 @@ jquery.waitforimages@^2.2.0:
resolved "https://registry.yarnpkg.com/jquery.waitforimages/-/jquery.waitforimages-2.2.0.tgz#63f23131055a1b060dc913e6d874bcc9b9e6b16b"
integrity sha1-Y/IxMQVaGwYNyRPm2HS8ybnmsWs=
-"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^3.2.1:
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
- integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
+"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^3.4.1:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
+ integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
js-base64@^2.1.8:
version "2.5.1"
@@ -10537,12 +10537,12 @@ tapable@^1.0.0, tapable@^1.1.0:
integrity sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==
tar@^2.0.0:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
- integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
+ integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==
dependencies:
block-stream "*"
- fstream "^1.0.2"
+ fstream "^1.0.12"
inherits "2"
tar@^4: