summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/commons/gitlab_ui.js4
-rw-r--r--app/assets/javascripts/diffs/components/diff_gutter_avatars.vue4
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue23
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue2
-rw-r--r--app/assets/javascripts/notes/constants.js2
-rw-r--r--app/assets/javascripts/notes/discussion_filters.js4
-rw-r--r--app/assets/javascripts/notes/stores/actions.js4
-rw-r--r--app/assets/javascripts/notes/stores/getters.js2
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js4
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_countdown.vue4
-rw-r--r--app/assets/stylesheets/framework/blocks.scss1
-rw-r--r--app/assets/stylesheets/framework/tables.scss6
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss8
-rw-r--r--app/finders/personal_access_tokens_finder.rb2
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/models/application_setting.rb11
-rw-r--r--app/models/ci/build.rb22
-rw-r--r--app/models/commit_status.rb9
-rw-r--r--app/models/deploy_token.rb5
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/pool_repository.rb22
-rw-r--r--app/models/postgresql/replication_slot.rb11
-rw-r--r--app/models/project.rb13
-rw-r--r--app/models/shard.rb26
-rw-r--r--app/models/user.rb6
-rw-r--r--app/models/user_preference.rb5
-rw-r--r--app/policies/ci/build_policy.rb7
-rw-r--r--app/policies/deployment_policy.rb9
-rw-r--r--app/presenters/commit_status_presenter.rb5
-rw-r--r--app/serializers/job_entity.rb1
-rw-r--r--app/serializers/user_preference_entity.rb4
-rw-r--r--app/services/ci/register_job_service.rb5
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml8
-rw-r--r--app/views/groups/labels/index.html.haml2
-rw-r--r--app/views/projects/deployments/_rollback.haml2
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml18
-rw-r--r--app/views/shared/empty_states/_labels.html.haml9
-rw-r--r--changelogs/unreleased/52300-pool-repositories.yml5
-rw-r--r--changelogs/unreleased/52925-scheduled-pipelines-ui-problems.yml5
-rw-r--r--changelogs/unreleased/53230-remove_personal_access_tokens_finder_find_by_method.yml5
-rw-r--r--changelogs/unreleased/53362-allow-concurrency-in-puma.yml5
-rw-r--r--changelogs/unreleased/disallow-retry-of-old-builds.yml5
-rw-r--r--changelogs/unreleased/gl-ui-tooltip.yml5
-rw-r--r--changelogs/unreleased/gt-update-project-and-group-labels-empty-state.yml5
-rw-r--r--changelogs/unreleased/issue_51323.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-52176.yml5
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/environments/production.rb2
-rw-r--r--config/initializers/fill_shards.rb4
-rw-r--r--db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb18
-rw-r--r--db/migrate/20181019032400_add_shards_table.rb11
-rw-r--r--db/migrate/20181019032408_add_repositories_table.rb15
-rw-r--r--db/migrate/20181019105553_add_projects_pool_repository_id_foreign_key.rb22
-rw-r--r--db/migrate/20181023104858_add_archive_builds_duration_to_application_settings.rb11
-rw-r--r--db/schema.rb20
-rw-r--r--lib/api/users.rb4
-rw-r--r--lib/gitlab/ci/status/build/failed.rb3
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/factories/ci/builds.rb6
-rw-r--r--spec/features/projects/environments/environment_spec.rb13
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb57
-rw-r--r--spec/finders/notes_finder_spec.rb10
-rw-r--r--spec/finders/personal_access_tokens_finder_spec.rb8
-rw-r--r--spec/fixtures/api/schemas/job/job.json6
-rw-r--r--spec/javascripts/notes/components/discussion_filter_spec.js24
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js7
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js13
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js10
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/models/application_setting_spec.rb20
-rw-r--r--spec/models/ci/build_spec.rb72
-rw-r--r--spec/models/postgresql/replication_slot_spec.rb20
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/shard_spec.rb50
-rw-r--r--spec/models/user_preference_spec.rb37
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb2
-rw-r--r--spec/services/ci/register_job_service_spec.rb14
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb16
83 files changed, 733 insertions, 97 deletions
diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js
index 82a191d056b..6c18a0fd390 100644
--- a/app/assets/javascripts/commons/gitlab_ui.js
+++ b/app/assets/javascripts/commons/gitlab_ui.js
@@ -1,6 +1,4 @@
import Vue from 'vue';
-import { GlLoadingIcon, GlTooltipDirective } from '@gitlab-org/gitlab-ui';
+import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
Vue.component('gl-loading-icon', GlLoadingIcon);
-
-Vue.directive('gl-tooltip', GlTooltipDirective);
diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index 1b59777f901..254bc235691 100644
--- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
+++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
@@ -3,6 +3,7 @@ import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import { pluralize, truncate } from '~/lib/utils/text_utility';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import { COUNT_OF_AVATARS_IN_GUTTER, LENGTH_OF_AVATAR_TOOLTIP } from '../constants';
export default {
@@ -10,6 +11,9 @@ export default {
Icon,
UserAvatarImage,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
discussions: {
type: Array,
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 6e8f43048d1..affa2d1b574 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -1,7 +1,8 @@
<script>
import $ from 'jquery';
-import Icon from '~/vue_shared/components/icon.vue';
import { mapGetters, mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE } from '../constants';
export default {
components: {
@@ -12,14 +13,17 @@ export default {
type: Array,
required: true,
},
- defaultValue: {
+ selectedValue: {
type: Number,
default: null,
required: false,
},
},
data() {
- return { currentValue: this.defaultValue };
+ return {
+ currentValue: this.selectedValue,
+ defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE,
+ };
},
computed: {
...mapGetters(['getNotesDataByProp']),
@@ -28,8 +32,11 @@ export default {
return this.filters.find(filter => filter.value === this.currentValue);
},
},
+ mounted() {
+ this.toggleCommentsForm();
+ },
methods: {
- ...mapActions(['filterDiscussion']),
+ ...mapActions(['filterDiscussion', 'setCommentsDisabled']),
selectFilter(value) {
const filter = parseInt(value, 10);
@@ -39,6 +46,10 @@ export default {
if (filter === this.currentValue) return;
this.currentValue = filter;
this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter });
+ this.toggleCommentsForm();
+ },
+ toggleCommentsForm() {
+ this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE);
},
},
};
@@ -73,6 +84,10 @@ export default {
>
{{ filter.title }}
</button>
+ <div
+ v-if="filter.value === defaultValue"
+ class="dropdown-divider"
+ ></div>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 7514ce8a1eb..ed5ac112dc0 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -60,6 +60,7 @@ export default {
'getNotesDataByProp',
'discussionCount',
'isLoading',
+ 'commentsDisabled',
]),
noteableType() {
return this.noteableData.noteableType;
@@ -206,6 +207,7 @@ export default {
</ul>
<comment-form
+ v-if="!commentsDisabled"
:noteable-type="noteableType"
:markdown-version="markdownVersion"
/>
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 2c3e07c0506..3147dc64c27 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -15,6 +15,8 @@ export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest';
export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
export const RESOLVE_NOTE_METHOD_NAME = 'post';
export const DESCRIPTION_TYPE = 'changed the description';
+export const HISTORY_ONLY_FILTER_VALUE = 2;
+export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0;
export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE,
diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js
index 06eadaeea0e..5c5f38a3fb0 100644
--- a/app/assets/javascripts/notes/discussion_filters.js
+++ b/app/assets/javascripts/notes/discussion_filters.js
@@ -6,7 +6,7 @@ export default store => {
if (discussionFilterEl) {
const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
- const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
+ const selectedValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
const filters = Object.keys(filterValues).map(entry => ({
title: entry,
@@ -24,7 +24,7 @@ export default store => {
return createElement('discussion-filter', {
props: {
filters,
- defaultValue,
+ selectedValue,
},
});
},
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index b5dd49bc6c9..88739ffb083 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -364,5 +364,9 @@ export const filterDiscussion = ({ dispatch }, { path, filter }) => {
});
};
+export const setCommentsDisabled = ({ commit }, data) => {
+ commit(types.DISABLE_COMMENTS, data);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index e4f36154fcd..8df95c279eb 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -192,5 +192,7 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
return getters.unresolvedDiscussionsIdsByDate[0];
};
+export const commentsDisabled = state => state.commentsDisabled;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 400142668ea..8aea269ea7d 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -21,6 +21,7 @@ export default () => ({
noteableData: {
current_user: {},
},
+ commentsDisabled: false,
},
actions,
getters,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 2fa53aef1d4..dfbf3b7b34b 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -15,6 +15,7 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
+export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
// DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 65085452139..c8d9e196103 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -225,4 +225,8 @@ export default {
discussion.truncated_diff_lines = diffLines;
},
+
+ [types.DISABLE_COMMENTS](state, value) {
+ state.commentsDisabled = value;
+ },
};
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index 8950ae31627..4d461baf74d 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -5,7 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import GfmAutoComplete from '~/gfm_auto_complete';
import { __, s__ } from '~/locale';
import Api from '~/api';
-import { GlModal } from '@gitlab-org/gitlab-ui';
+import { GlModal, GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import eventHub from './event_hub';
import EmojiMenuInModal from './emoji_menu_in_modal';
@@ -16,6 +16,9 @@ export default {
Icon,
GlModal,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
currentEmoji: {
type: String,
diff --git a/app/assets/javascripts/vue_shared/components/gl_countdown.vue b/app/assets/javascripts/vue_shared/components/gl_countdown.vue
index 9327a2a4a6c..a35986b2d03 100644
--- a/app/assets/javascripts/vue_shared/components/gl_countdown.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_countdown.vue
@@ -1,10 +1,14 @@
<script>
import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
/**
* Counts down to a given end date.
*/
export default {
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
endDateString: {
type: String,
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index f26b1fddae5..43b7c26b272 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -348,6 +348,7 @@
@include media-breakpoint-down(xs) {
width: 100%;
+ margin: $btn-side-margin 0;
}
}
}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
index 339388392df..6954e6599b1 100644
--- a/app/assets/stylesheets/framework/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -147,3 +147,9 @@ table {
}
}
}
+
+.top-area + .content-list {
+ th {
+ border-top: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index 86e70955389..617b3db2fae 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -39,10 +39,6 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-
- svg {
- vertical-align: middle;
- }
}
.next-run-cell {
@@ -52,6 +48,10 @@
a {
color: $text-color;
}
+
+ svg {
+ vertical-align: middle;
+ }
}
.pipeline-schedules-user-callout {
diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb
index 81fd3b7a547..bd95dcd323f 100644
--- a/app/finders/personal_access_tokens_finder.rb
+++ b/app/finders/personal_access_tokens_finder.rb
@@ -3,7 +3,7 @@
class PersonalAccessTokensFinder
attr_accessor :params
- delegate :build, :find, :find_by, :find_by_token, to: :execute
+ delegate :build, :find, :find_by_id, :find_by_token, to: :execute
def initialize(params = {})
@params = params
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 0c9f69b6714..9a1c2a4c9e1 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -115,6 +115,7 @@ module ApplicationSettingsHelper
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
+ :archive_builds_in_human_readable,
:authorized_keys_enabled,
:auto_devops_enabled,
:auto_devops_domain,
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index b66ec0ffab6..704310f53f0 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -5,6 +5,7 @@ class ApplicationSetting < ActiveRecord::Base
include CacheMarkdownField
include TokenAuthenticatable
include IgnorableColumn
+ include ChronicDurationAttribute
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
@@ -45,6 +46,8 @@ class ApplicationSetting < ActiveRecord::Base
default_value_for :id, 1
+ chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
+
validates :uuid, presence: true
validates :session_expire_delay,
@@ -184,6 +187,10 @@ class ApplicationSetting < ActiveRecord::Base
validates :user_default_internal_regex, js_regex: true, allow_nil: true
+ validates :archive_builds_in_seconds,
+ allow_nil: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds }
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
@@ -441,6 +448,10 @@ class ApplicationSetting < ActiveRecord::Base
latest_terms
end
+ def archive_builds_older_than
+ archive_builds_in_seconds.seconds.ago if archive_builds_in_seconds
+ end
+
private
def ensure_uuid!
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 600c562d05a..d7eab57763e 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -258,8 +258,24 @@ module Ci
self.name == 'pages'
end
+ # degenerated build is one that cannot be run by Runner
+ def degenerated?
+ self.options.nil?
+ end
+
+ def degenerate!
+ self.update!(options: nil, yaml_variables: nil, commands: nil)
+ end
+
+ def archived?
+ return true if degenerated?
+
+ archive_builds_older_than = Gitlab::CurrentSettings.current_application_settings.archive_builds_older_than
+ archive_builds_older_than.present? && created_at < archive_builds_older_than
+ end
+
def playable?
- action? && (manual? || scheduled? || retryable?)
+ action? && !archived? && (manual? || scheduled? || retryable?)
end
def schedulable?
@@ -287,7 +303,7 @@ module Ci
end
def retryable?
- success? || failed? || canceled?
+ !archived? && (success? || failed? || canceled?)
end
def retries_count
@@ -295,7 +311,7 @@ module Ci
end
def retries_max
- self.options.fetch(:retry, 0).to_i
+ self.options.to_h.fetch(:retry, 0).to_i
end
def latest?
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 95c88e11a6e..755f8bd4d06 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -51,7 +51,8 @@ class CommitStatus < ActiveRecord::Base
missing_dependency_failure: 5,
runner_unsupported: 6,
stale_schedule: 7,
- job_execution_timeout: 8
+ job_execution_timeout: 8,
+ archived_failure: 9
}
##
@@ -167,16 +168,18 @@ class CommitStatus < ActiveRecord::Base
false
end
- # To be overridden when inherrited from
def retryable?
false
end
- # To be overridden when inherrited from
def cancelable?
false
end
+ def archived?
+ false
+ end
+
def stuck?
false
end
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
index 0b2eedf3631..e3524305346 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -4,6 +4,7 @@ class DeployToken < ActiveRecord::Base
include Expirable
include TokenAuthenticatable
include PolicyActor
+ include Gitlab::Utils::StrongMemoize
add_authentication_token_field :token
AVAILABLE_SCOPES = %i(read_repository read_registry).freeze
@@ -49,7 +50,9 @@ class DeployToken < ActiveRecord::Base
# to a single project, later we're going to extend
# that to be for multiple projects and namespaces.
def project
- projects.first
+ strong_memoize(:project) do
+ projects.first
+ end
end
def expires_at
diff --git a/app/models/note.rb b/app/models/note.rb
index 990689a95f5..592efb714f3 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -117,6 +117,8 @@ class Note < ActiveRecord::Base
case notes_filter
when UserPreference::NOTES_FILTERS[:only_comments]
user
+ when UserPreference::NOTES_FILTERS[:only_activity]
+ system
else
all
end
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
new file mode 100644
index 00000000000..8ef74539209
--- /dev/null
+++ b/app/models/pool_repository.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class PoolRepository < ActiveRecord::Base
+ POOL_PREFIX = '@pools'
+
+ belongs_to :shard
+ validates :shard, presence: true
+
+ # For now, only pool repositories are tracked in the database. However, we may
+ # want to add other repository types in the future
+ self.table_name = 'repositories'
+
+ has_many :pool_member_projects, class_name: 'Project', foreign_key: :pool_repository_id
+
+ def shard_name
+ shard&.name
+ end
+
+ def shard_name=(name)
+ self.shard = Shard.by_name(name)
+ end
+end
diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb
index 70c7432e6b5..e264fe88e47 100644
--- a/app/models/postgresql/replication_slot.rb
+++ b/app/models/postgresql/replication_slot.rb
@@ -4,6 +4,15 @@ module Postgresql
class ReplicationSlot < ActiveRecord::Base
self.table_name = 'pg_replication_slots'
+ # Returns true if there are any replication slots in use.
+ # PostgreSQL-compatible databases such as Aurora don't support
+ # replication slots, so this will return false as well.
+ def self.in_use?
+ transaction { exists? }
+ rescue ActiveRecord::StatementInvalid
+ false
+ end
+
# Returns true if the lag observed across all replication slots exceeds a
# given threshold.
#
@@ -11,6 +20,8 @@ module Postgresql
# statistics it takes between 1 and 5 seconds to replicate around
# 100 MB of data.
def self.lag_too_great?(max = 100.megabytes)
+ return false unless in_use?
+
lag_function = "#{Gitlab::Database.pg_wal_lsn_diff}" \
"(#{Gitlab::Database.pg_current_wal_insert_lsn}(), restart_lsn)::bigint"
diff --git a/app/models/project.rb b/app/models/project.rb
index fa995b5b061..d3b148d0ac0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -95,8 +95,7 @@ class Project < ActiveRecord::Base
unless: :ci_cd_settings,
if: proc { ProjectCiCdSetting.available? }
- after_create :set_last_activity_at
- after_create :set_last_repository_updated_at
+ after_create :set_timestamps_for_create
after_update :update_forks_visibility_level
before_destroy :remove_private_deploy_keys
@@ -124,6 +123,7 @@ class Project < ActiveRecord::Base
alias_attribute :title, :name
# Relations
+ belongs_to :pool_repository
belongs_to :creator, class_name: 'User'
belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
belongs_to :namespace
@@ -2102,13 +2102,8 @@ class Project < ActiveRecord::Base
gitlab_shell.exists?(repository_storage, "#{disk_path}.git")
end
- # set last_activity_at to the same as created_at
- def set_last_activity_at
- update_column(:last_activity_at, self.created_at)
- end
-
- def set_last_repository_updated_at
- update_column(:last_repository_updated_at, self.created_at)
+ def set_timestamps_for_create
+ update_columns(last_activity_at: self.created_at, last_repository_updated_at: self.created_at)
end
def cross_namespace_reference?(from)
diff --git a/app/models/shard.rb b/app/models/shard.rb
new file mode 100644
index 00000000000..2fa22bd040c
--- /dev/null
+++ b/app/models/shard.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class Shard < ActiveRecord::Base
+ # Store shard names from the configuration file in the database. This is not a
+ # list of active shards - we just want to assign an immutable, unique ID to
+ # every shard name for easy indexing / referencing.
+ def self.populate!
+ return unless table_exists?
+
+ # The GitLab config does not change for the lifecycle of the process
+ in_config = Gitlab.config.repositories.storages.keys.map(&:to_s)
+
+ transaction do
+ in_db = all.pluck(:name)
+ missing = in_config - in_db
+
+ missing.map { |name| by_name(name) }
+ end
+ end
+
+ def self.by_name(name)
+ find_or_create_by(name: name)
+ rescue ActiveRecord::RecordNotUnique
+ retry
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index d3eb7162174..039a3854edb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -460,12 +460,6 @@ class User < ActiveRecord::Base
by_username(username).take!
end
- def find_by_personal_access_token(token_string)
- return unless token_string
-
- PersonalAccessTokensFinder.new(state: 'active').find_by_token(token_string)&.user # rubocop: disable CodeReuse/Finder
- end
-
# Returns a user for the given SSH key.
def find_by_ssh_key_id(key_id)
Key.find_by(id: key_id)&.user
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index 6cd91abc261..32d0407800f 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -4,7 +4,7 @@ class UserPreference < ActiveRecord::Base
# We could use enums, but Rails 4 doesn't support multiple
# enum options with same name for multiple fields, also it creates
# extra methods that aren't really needed here.
- NOTES_FILTERS = { all_notes: 0, only_comments: 1 }.freeze
+ NOTES_FILTERS = { all_notes: 0, only_comments: 1, only_activity: 2 }.freeze
belongs_to :user
@@ -14,7 +14,8 @@ class UserPreference < ActiveRecord::Base
def notes_filters
{
s_('Notes|Show all activity') => NOTES_FILTERS[:all_notes],
- s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments]
+ s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments],
+ s_('Notes|Show history only') => NOTES_FILTERS[:only_activity]
}
end
end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 3858b29c82c..0ca3e696f46 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -20,12 +20,17 @@ module Ci
@subject.project.branch_allows_collaboration?(@user, @subject.ref)
end
+ condition(:archived, scope: :subject) do
+ @subject.archived?
+ end
+
condition(:terminal, scope: :subject) do
@subject.has_terminal?
end
- rule { protected_ref }.policy do
+ rule { protected_ref | archived }.policy do
prevent :update_build
+ prevent :update_commit_status
prevent :erase_build
end
diff --git a/app/policies/deployment_policy.rb b/app/policies/deployment_policy.rb
index 56ac898b6ab..d4f2f3c52b1 100644
--- a/app/policies/deployment_policy.rb
+++ b/app/policies/deployment_policy.rb
@@ -2,4 +2,13 @@
class DeploymentPolicy < BasePolicy
delegate { @subject.project }
+
+ condition(:can_retry_deployable) do
+ can?(:update_build, @subject.deployable)
+ end
+
+ rule { ~can_retry_deployable }.policy do
+ prevent :create_deployment
+ prevent :update_deployment
+ end
end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index a866e76df5a..0cd77da6303 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -10,7 +10,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
missing_dependency_failure: 'There has been a missing dependency failure',
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
stale_schedule: 'Delayed job could not be executed by some reason, please try again',
- job_execution_timeout: 'The script exceeded the maximum execution time set for the job'
+ job_execution_timeout: 'The script exceeded the maximum execution time set for the job',
+ archived_failure: 'The job is archived and cannot be run'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
@@ -30,6 +31,6 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
end
def unrecoverable?
- script_failure? || missing_dependency_failure?
+ script_failure? || missing_dependency_failure? || archived_failure?
end
end
diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb
index aebbc18e32f..d0099ae77f2 100644
--- a/app/serializers/job_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -7,6 +7,7 @@ class JobEntity < Grape::Entity
expose :name
expose :started?, as: :started
+ expose :archived?, as: :archived
expose :build_path do |build|
build_path(build)
diff --git a/app/serializers/user_preference_entity.rb b/app/serializers/user_preference_entity.rb
index fbdaab459b3..b99f80424db 100644
--- a/app/serializers/user_preference_entity.rb
+++ b/app/serializers/user_preference_entity.rb
@@ -7,4 +7,8 @@ class UserPreferenceEntity < Grape::Entity
expose :notes_filters do |user_preference|
UserPreference.notes_filters
end
+
+ expose :default_notes_filter do |user_preference|
+ UserPreference::NOTES_FILTERS[:all_notes]
+ end
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 5a7be921389..e06f1c05843 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -82,6 +82,11 @@ module Ci
return false
end
+ if build.archived?
+ build.drop!(:archived_failure)
+ return false
+ end
+
build.run!
true
end
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 97be658cd34..adb496495d1 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -41,5 +41,13 @@
The default unit is in seconds, but you can define an alternative. For example:
<code>4 mins 2 sec</code>, <code>2h42min</code>.
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
+ .form-group
+ = f.label :archive_builds_in_human_readable, 'Archive builds in', class: 'label-bold'
+ = f.text_field :archive_builds_in_human_readable, class: 'form-control', placeholder: 'never'
+ .form-text.text-muted
+ Set the duration when build gonna be considered old. Archived builds cannot be retried.
+ Make it empty to never expire builds. It has to be larger than 1 day.
+ The default unit is in seconds, but you can define an alternative. For example:
+ <code>4 mins 2 sec</code>, <code>2h42min</code>.
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 5b78ce910b8..4df3d831942 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -6,7 +6,7 @@
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
-- if can_admin_label
+- if @labels.present? && can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_group_label_path(@group), class: "btn btn-success"
diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml
index 281e042c915..1bd538a08ff 100644
--- a/app/views/projects/deployments/_rollback.haml
+++ b/app/views/projects/deployments/_rollback.haml
@@ -1,4 +1,4 @@
-- if can?(current_user, :create_deployment, deployment) && deployment.deployable
+- if can?(current_user, :create_deployment, deployment)
- tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment')
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build has-tooltip', title: tooltip do
- if deployment.last?
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 06ee883d6dc..2c6484c2c99 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -5,7 +5,7 @@
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
-- if can_admin_label
+- if @labels.present? && can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 95bba47802c..66e202103a9 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -61,12 +61,14 @@
%td.responsive-table-cell.build-failure{ data: { column: _("Failure")} }
= build.present.callout_failure_message
%td.responsive-table-cell.build-actions
- = link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build' do
- = icon('repeat')
- %tr.build-trace-row.responsive-table-border-end
- %td
- %td.responsive-table-cell.build-trace-container{ colspan: 4 }
- %pre.build-trace.build-trace-rounded
- %code.bash.js-build-output
- = build_summary(build)
+ - if can?(current_user, :update_build, job)
+ = link_to retry_project_job_path(build.project, build, return_to: request.original_url), method: :post, title: _('Retry'), class: 'btn btn-build' do
+ = icon('repeat')
+ - if can?(current_user, :read_build, job)
+ %tr.build-trace-row.responsive-table-border-end
+ %td
+ %td.responsive-table-cell.build-trace-container{ colspan: 4 }
+ %pre.build-trace.build-trace-rounded
+ %code.bash.js-build-output
+ = build_summary(build)
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index b629ceafeb3..9133ce8ed22 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -6,6 +6,9 @@
.text-content
%h4= _("Labels can be applied to issues and merge requests to categorize them.")
%p= _("You can also star a label to make it a priority label.")
- - if can?(current_user, :admin_label, @project)
- = link_to _('New label'), new_project_label_path(@project), class: 'btn btn-success', title: _('New label'), id: 'new_label_link'
- = link_to _('Generate a default set of labels'), generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: _('Generate a default set of labels'), id: 'generate_labels_link'
+ .text-center
+ - if can?(current_user, :admin_label, @project)
+ = link_to _('New label'), new_project_label_path(@project), class: 'btn btn-success', title: _('New label'), id: 'new_label_link'
+ = link_to _('Generate a default set of labels'), generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: _('Generate a default set of labels'), id: 'generate_labels_link'
+ - if can?(current_user, :admin_label, @group)
+ = link_to _('New label'), new_group_label_path(@group), class: 'btn btn-success', title: _('New label'), id: 'new_label_link'
diff --git a/changelogs/unreleased/52300-pool-repositories.yml b/changelogs/unreleased/52300-pool-repositories.yml
new file mode 100644
index 00000000000..5435f3aa21f
--- /dev/null
+++ b/changelogs/unreleased/52300-pool-repositories.yml
@@ -0,0 +1,5 @@
+---
+title: Start tracking shards and pool repositories in the database
+merge_request: 22482
+author:
+type: other
diff --git a/changelogs/unreleased/52925-scheduled-pipelines-ui-problems.yml b/changelogs/unreleased/52925-scheduled-pipelines-ui-problems.yml
new file mode 100644
index 00000000000..792b24d75ac
--- /dev/null
+++ b/changelogs/unreleased/52925-scheduled-pipelines-ui-problems.yml
@@ -0,0 +1,5 @@
+---
+title: Fixing styling issues on the scheduled pipelines page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/53230-remove_personal_access_tokens_finder_find_by_method.yml b/changelogs/unreleased/53230-remove_personal_access_tokens_finder_find_by_method.yml
new file mode 100644
index 00000000000..d4d78a2fd06
--- /dev/null
+++ b/changelogs/unreleased/53230-remove_personal_access_tokens_finder_find_by_method.yml
@@ -0,0 +1,5 @@
+---
+title: Remove PersonalAccessTokensFinder#find_by method
+merge_request: 22617
+author:
+type: fixed
diff --git a/changelogs/unreleased/53362-allow-concurrency-in-puma.yml b/changelogs/unreleased/53362-allow-concurrency-in-puma.yml
new file mode 100644
index 00000000000..5fbda0161c1
--- /dev/null
+++ b/changelogs/unreleased/53362-allow-concurrency-in-puma.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Rails concurrency when running in Puma
+merge_request: 22751
+author:
+type: performance
diff --git a/changelogs/unreleased/disallow-retry-of-old-builds.yml b/changelogs/unreleased/disallow-retry-of-old-builds.yml
new file mode 100644
index 00000000000..03992fc0213
--- /dev/null
+++ b/changelogs/unreleased/disallow-retry-of-old-builds.yml
@@ -0,0 +1,5 @@
+---
+title: Soft-archive old jobs
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/gl-ui-tooltip.yml b/changelogs/unreleased/gl-ui-tooltip.yml
new file mode 100644
index 00000000000..99ded9f812e
--- /dev/null
+++ b/changelogs/unreleased/gl-ui-tooltip.yml
@@ -0,0 +1,5 @@
+---
+title: Remove gitlab-ui's tooltip from global
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/gt-update-project-and-group-labels-empty-state.yml b/changelogs/unreleased/gt-update-project-and-group-labels-empty-state.yml
new file mode 100644
index 00000000000..d644ca86b79
--- /dev/null
+++ b/changelogs/unreleased/gt-update-project-and-group-labels-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Update project and group labels empty state
+merge_request: 22745
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/issue_51323.yml b/changelogs/unreleased/issue_51323.yml
new file mode 100644
index 00000000000..b0e83e303d1
--- /dev/null
+++ b/changelogs/unreleased/issue_51323.yml
@@ -0,0 +1,5 @@
+---
+title: Add 'only history' option to notes filter
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-fix-issue-52176.yml b/changelogs/unreleased/sh-fix-issue-52176.yml
new file mode 100644
index 00000000000..7269e14d910
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-52176.yml
@@ -0,0 +1,5 @@
+---
+title: Disable replication lag check for Aurora PostgreSQL databases
+merge_request: 22786
+author:
+type: fixed
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 23790b84e3c..494ddd72556 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -45,4 +45,6 @@ Rails.application.configure do
# Do not log asset requests
config.assets.quiet = true
+
+ config.allow_concurrency = defined?(::Puma)
end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 9941987929c..71195164e7a 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -83,5 +83,5 @@ Rails.application.configure do
config.eager_load = true
- config.allow_concurrency = false
+ config.allow_concurrency = defined?(::Puma)
end
diff --git a/config/initializers/fill_shards.rb b/config/initializers/fill_shards.rb
new file mode 100644
index 00000000000..0f45cf44621
--- /dev/null
+++ b/config/initializers/fill_shards.rb
@@ -0,0 +1,4 @@
+return unless Shard.connected?
+return if Gitlab::Database.read_only?
+
+Shard.populate!
diff --git a/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb b/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb
new file mode 100644
index 00000000000..61d32fe16eb
--- /dev/null
+++ b/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexToProjectDeployTokensDeployTokenId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # MySQL already has index inserted
+ add_concurrent_index :project_deploy_tokens, :deploy_token_id if Gitlab::Database.postgresql?
+ end
+
+ def down
+ remove_concurrent_index(:project_deploy_tokens, :deploy_token_id) if Gitlab::Database.postgresql?
+ end
+end
diff --git a/db/migrate/20181019032400_add_shards_table.rb b/db/migrate/20181019032400_add_shards_table.rb
new file mode 100644
index 00000000000..5e0a6960548
--- /dev/null
+++ b/db/migrate/20181019032400_add_shards_table.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddShardsTable < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :shards do |t|
+ t.string :name, null: false, index: { unique: true }
+ end
+ end
+end
diff --git a/db/migrate/20181019032408_add_repositories_table.rb b/db/migrate/20181019032408_add_repositories_table.rb
new file mode 100644
index 00000000000..077f264d3ce
--- /dev/null
+++ b/db/migrate/20181019032408_add_repositories_table.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddRepositoriesTable < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :repositories, id: :bigserial do |t|
+ t.references :shard, null: false, index: true, foreign_key: { on_delete: :restrict }
+ t.string :disk_path, null: false, index: { unique: true }
+ end
+
+ add_column :projects, :pool_repository_id, :bigint
+ add_index :projects, :pool_repository_id, where: 'pool_repository_id IS NOT NULL'
+ end
+end
diff --git a/db/migrate/20181019105553_add_projects_pool_repository_id_foreign_key.rb b/db/migrate/20181019105553_add_projects_pool_repository_id_foreign_key.rb
new file mode 100644
index 00000000000..059988de38a
--- /dev/null
+++ b/db/migrate/20181019105553_add_projects_pool_repository_id_foreign_key.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddProjectsPoolRepositoryIdForeignKey < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key(
+ :projects,
+ :repositories,
+ column: :pool_repository_id,
+ on_delete: :nullify
+ )
+ end
+
+ def down
+ remove_foreign_key(:projects, column: :pool_repository_id)
+ end
+end
diff --git a/db/migrate/20181023104858_add_archive_builds_duration_to_application_settings.rb b/db/migrate/20181023104858_add_archive_builds_duration_to_application_settings.rb
new file mode 100644
index 00000000000..744748b3fad
--- /dev/null
+++ b/db/migrate/20181023104858_add_archive_builds_duration_to_application_settings.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddArchiveBuildsDurationToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column(:application_settings, :archive_builds_in_seconds, :integer, allow_null: true)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1a8b556228d..32d10e87e87 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -165,6 +165,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do
t.integer "usage_stats_set_by_user_id"
t.integer "receive_max_input_size"
t.integer "diff_max_patch_bytes", default: 102400, null: false
+ t.integer "archive_builds_in_seconds"
end
create_table "audit_events", force: :cascade do |t|
@@ -1588,6 +1589,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do
t.datetime_with_timezone "created_at", null: false
end
+ add_index "project_deploy_tokens", ["deploy_token_id"], name: "index_project_deploy_tokens_on_deploy_token_id", using: :btree
add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
create_table "project_features", force: :cascade do |t|
@@ -1703,6 +1705,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do
t.integer "jobs_cache_index"
t.boolean "pages_https_only", default: true
t.boolean "remote_mirror_available_overridden"
+ t.integer "pool_repository_id", limit: 8
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
@@ -1719,6 +1722,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
+ add_index "projects", ["pool_repository_id"], name: "index_projects_on_pool_repository_id", where: "(pool_repository_id IS NOT NULL)", using: :btree
add_index "projects", ["repository_storage", "created_at"], name: "idx_project_repository_check_partial", where: "(last_repository_check_at IS NULL)", using: :btree
add_index "projects", ["repository_storage"], name: "index_projects_on_repository_storage", using: :btree
add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
@@ -1851,6 +1855,14 @@ ActiveRecord::Schema.define(version: 20181101144347) do
add_index "remote_mirrors", ["last_successful_update_at"], name: "index_remote_mirrors_on_last_successful_update_at", using: :btree
add_index "remote_mirrors", ["project_id"], name: "index_remote_mirrors_on_project_id", using: :btree
+ create_table "repositories", id: :bigserial, force: :cascade do |t|
+ t.integer "shard_id", null: false
+ t.string "disk_path", null: false
+ end
+
+ add_index "repositories", ["disk_path"], name: "index_repositories_on_disk_path", unique: true, using: :btree
+ add_index "repositories", ["shard_id"], name: "index_repositories_on_shard_id", using: :btree
+
create_table "repository_languages", id: false, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "programming_language_id", null: false
@@ -1931,6 +1943,12 @@ ActiveRecord::Schema.define(version: 20181101144347) do
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
add_index "services", ["template"], name: "index_services_on_template", using: :btree
+ create_table "shards", force: :cascade do |t|
+ t.string "name", null: false
+ end
+
+ add_index "shards", ["name"], name: "index_shards_on_name", unique: true, using: :btree
+
create_table "site_statistics", force: :cascade do |t|
t.integer "repositories_count", default: 0, null: false
end
@@ -2450,6 +2468,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
add_foreign_key "project_mirror_data", "projects", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
+ add_foreign_key "projects", "repositories", column: "pool_repository_id", name: "fk_6e5c14658a", on_delete: :nullify
add_foreign_key "prometheus_metrics", "projects", on_delete: :cascade
add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade
add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade
@@ -2461,6 +2480,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do
add_foreign_key "push_event_payloads", "events", name: "fk_36c74129da", on_delete: :cascade
add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
add_foreign_key "remote_mirrors", "projects", on_delete: :cascade
+ add_foreign_key "repositories", "shards", on_delete: :restrict
add_foreign_key "repository_languages", "projects", on_delete: :cascade
add_foreign_key "resource_label_events", "issues", on_delete: :cascade
add_foreign_key "resource_label_events", "labels", on_delete: :nullify
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 47382b09207..2a56506f3a5 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -512,11 +512,9 @@ module API
PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
end
- # rubocop: disable CodeReuse/ActiveRecord
def find_impersonation_token
- finder.find_by(id: declared_params[:impersonation_token_id]) || not_found!('Impersonation Token')
+ finder.find_by_id(declared_params[:impersonation_token_id]) || not_found!('Impersonation Token')
end
- # rubocop: enable CodeReuse/ActiveRecord
end
before { authenticated_as_admin! }
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index 7cc1cc6b8e3..d40454df737 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -14,7 +14,8 @@ module Gitlab
missing_dependency_failure: 'missing dependency failure',
runner_unsupported: 'unsupported runner',
stale_schedule: 'stale schedule',
- job_execution_timeout: 'job execution timeout'
+ job_execution_timeout: 'job execution timeout',
+ archived_failure: 'archived failure'
}.freeze
private_constant :REASONS
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 2bed470514b..9790818ecaf 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -92,6 +92,7 @@ excluded_attributes:
- :path
- :namespace_id
- :creator_id
+ - :pool_repository_id
- :import_url
- :import_status
- :avatar
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 708e74941cd..2f4b0e900c3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4165,6 +4165,9 @@ msgstr ""
msgid "Notes|Show comments only"
msgstr ""
+msgid "Notes|Show history only"
+msgstr ""
+
msgid "Notification events"
msgstr ""
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 85ba7d4097d..0cacdf7931f 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -27,6 +27,12 @@ FactoryBot.define do
pipeline factory: :ci_pipeline
+ trait :degenerated do
+ commands nil
+ options nil
+ yaml_variables nil
+ end
+
trait :started do
started_at 'Di 29. Okt 09:51:28 CET 2013'
end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 70e0879dd81..4f8f67aab22 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -53,10 +53,21 @@ describe 'Environment' do
it 'does show build name' do
expect(page).to have_link("#{build.name} (##{build.id})")
- expect(page).to have_link('Re-deploy')
+ expect(page).not_to have_link('Re-deploy')
expect(page).not_to have_terminal_button
end
+ context 'when user has ability to re-deploy' do
+ let(:permissions) do
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
+ end
+
+ it 'does show re-deploy' do
+ expect(page).to have_link('Re-deploy')
+ end
+ end
+
context 'with manual action' do
let(:action) do
create(:ci_build, :manual, pipeline: pipeline,
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index cd6c37bf54d..049bbca958f 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -388,54 +388,83 @@ describe 'Pipeline', :js do
let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) }
+ subject { visit pipeline_failures_page }
+
context 'with failed build' do
before do
failed_build.trace.set('4 examples, 1 failure')
-
- visit pipeline_failures_page
end
it 'shows jobs tab pane as active' do
+ subject
+
expect(page).to have_content('Failed Jobs')
expect(page).to have_css('#js-tab-failures.active')
end
it 'lists failed builds' do
+ subject
+
expect(page).to have_content(failed_build.name)
expect(page).to have_content(failed_build.stage)
end
it 'shows build failure logs' do
+ subject
+
expect(page).to have_content('4 examples, 1 failure')
end
it 'shows the failure reason' do
+ subject
+
expect(page).to have_content('There is an unknown failure, please try again')
end
- it 'shows retry button for failed build' do
- page.within(find('.build-failures', match: :first)) do
- expect(page).to have_link('Retry')
+ context 'when user does not have permission to retry build' do
+ it 'shows retry button for failed build' do
+ subject
+
+ page.within(find('.build-failures', match: :first)) do
+ expect(page).not_to have_link('Retry')
+ end
end
end
- end
- context 'when missing build logs' do
- before do
- visit pipeline_failures_page
+ context 'when user does have permission to retry build' do
+ before do
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'shows retry button for failed build' do
+ subject
+
+ page.within(find('.build-failures', match: :first)) do
+ expect(page).to have_link('Retry')
+ end
+ end
end
+ end
+ context 'when missing build logs' do
it 'shows jobs tab pane as active' do
+ subject
+
expect(page).to have_content('Failed Jobs')
expect(page).to have_css('#js-tab-failures.active')
end
it 'lists failed builds' do
+ subject
+
expect(page).to have_content(failed_build.name)
expect(page).to have_content(failed_build.stage)
end
it 'does not show trace' do
+ subject
+
expect(page).to have_content('No job trace')
end
end
@@ -448,11 +477,9 @@ describe 'Pipeline', :js do
end
context 'when accessing failed jobs page' do
- before do
- visit pipeline_failures_page
- end
-
it 'fails to access the page' do
+ subject
+
expect(page).to have_title('Access Denied')
end
end
@@ -461,11 +488,11 @@ describe 'Pipeline', :js do
context 'without failures' do
before do
failed_build.update!(status: :success)
-
- visit pipeline_failures_page
end
it 'displays the pipeline graph' do
+ subject
+
expect(current_path).to eq(pipeline_path(pipeline))
expect(page).not_to have_content('Failed Jobs')
expect(page).to have_selector('.pipeline-visualization')
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index de9974c45e1..b51f1955ac4 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -13,7 +13,7 @@ describe NotesFinder do
let!(:comment) { create(:note_on_issue, project: project) }
let!(:system_note) { create(:note_on_issue, project: project, system: true) }
- it 'filters system notes' do
+ it 'returns only user notes when using only_comments filter' do
finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments])
notes = finder.execute
@@ -21,6 +21,14 @@ describe NotesFinder do
expect(notes).to match_array(comment)
end
+ it 'returns only system notes when using only_activity filters' do
+ finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
+
+ notes = finder.execute
+
+ expect(notes).to match_array(system_note)
+ end
+
it 'gets all notes' do
finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity])
diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb
index 3f22b3a253d..3e849c9a644 100644
--- a/spec/finders/personal_access_tokens_finder_spec.rb
+++ b/spec/finders/personal_access_tokens_finder_spec.rb
@@ -92,7 +92,7 @@ describe PersonalAccessTokensFinder do
end
describe 'with id' do
- subject { finder(params).find_by(id: active_personal_access_token.id) }
+ subject { finder(params).find_by_id(active_personal_access_token.id) }
it { is_expected.to eq(active_personal_access_token) }
@@ -106,7 +106,7 @@ describe PersonalAccessTokensFinder do
end
describe 'with token' do
- subject { finder(params).find_by(token: active_personal_access_token.token) }
+ subject { finder(params).find_by_token(active_personal_access_token.token) }
it { is_expected.to eq(active_personal_access_token) }
@@ -207,7 +207,7 @@ describe PersonalAccessTokensFinder do
end
describe 'with id' do
- subject { finder(params).find_by(id: active_personal_access_token.id) }
+ subject { finder(params).find_by_id(active_personal_access_token.id) }
it { is_expected.to eq(active_personal_access_token) }
@@ -221,7 +221,7 @@ describe PersonalAccessTokensFinder do
end
describe 'with token' do
- subject { finder(params).find_by(token: active_personal_access_token.token) }
+ subject { finder(params).find_by_token(active_personal_access_token.token) }
it { is_expected.to eq(active_personal_access_token) }
diff --git a/spec/fixtures/api/schemas/job/job.json b/spec/fixtures/api/schemas/job/job.json
index 734c535ef70..f3d5e9b038a 100644
--- a/spec/fixtures/api/schemas/job/job.json
+++ b/spec/fixtures/api/schemas/job/job.json
@@ -9,7 +9,8 @@
"playable",
"created_at",
"updated_at",
- "status"
+ "status",
+ "archived"
],
"properties": {
"id": { "type": "integer" },
@@ -27,7 +28,8 @@
"updated_at": { "type": "string" },
"status": { "$ref": "../status/ci_detailed_status.json" },
"callout_message": { "type": "string" },
- "recoverable": { "type": "boolean" }
+ "recoverable": { "type": "boolean" },
+ "archived": { "type": "boolean" }
},
"additionalProperties": true
}
diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js
index a81bdf618a3..9070d968cfd 100644
--- a/spec/javascripts/notes/components/discussion_filter_spec.js
+++ b/spec/javascripts/notes/components/discussion_filter_spec.js
@@ -19,7 +19,7 @@ describe('DiscussionFilter component', () => {
},
];
const Component = Vue.extend(DiscussionFilter);
- const defaultValue = discussionFiltersMock[0].value;
+ const selectedValue = discussionFiltersMock[0].value;
store.state.discussions = discussions;
vm = mountComponentWithStore(Component, {
@@ -27,7 +27,7 @@ describe('DiscussionFilter component', () => {
store,
props: {
filters: discussionFiltersMock,
- defaultValue,
+ selectedValue,
},
});
});
@@ -63,4 +63,24 @@ describe('DiscussionFilter component', () => {
expect(vm.filterDiscussion).not.toHaveBeenCalled();
});
+
+ it('disables commenting when "Show history only" filter is applied', () => {
+ const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ filterItem.click();
+
+ expect(vm.$store.state.commentsDisabled).toBe(true);
+ });
+
+ it('enables commenting when "Show history only" filter is not applied', () => {
+ const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
+ filterItem.click();
+
+ expect(vm.$store.state.commentsDisabled).toBe(false);
+ });
+
+ it('renders a dropdown divider for the default filter', () => {
+ const defaultFilter = vm.$el.querySelector('.dropdown-menu li:first-child');
+
+ expect(defaultFilter.lastChild.classList).toContain('dropdown-divider');
+ });
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 3e289a6b8e6..0081f42c330 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -121,6 +121,13 @@ describe('note_app', () => {
).toEqual('Write a comment or drag your files here…');
});
+ it('should not render form when commenting is disabled', () => {
+ store.state.commentsDisabled = true;
+ vm = mountComponent();
+
+ expect(vm.$el.querySelector('.js-main-target-form')).toEqual(null);
+ });
+
it('should render form comment button as disabled', () => {
expect(vm.$el.querySelector('.js-note-new-discussion').getAttribute('disabled')).toEqual(
'disabled',
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index f4643fd55ed..0c0bc45b201 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -509,4 +509,17 @@ describe('Actions Notes Store', () => {
expect(mrWidgetEventHub.$emit).toHaveBeenCalledWith('mr.discussion.updated');
});
});
+
+ describe('setCommentsDisabled', () => {
+ it('should set comments disabled state', done => {
+ testAction(
+ actions.setCommentsDisabled,
+ true,
+ null,
+ [{ type: 'DISABLE_COMMENTS', payload: true }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 380ab59099d..461de5a3106 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -427,4 +427,14 @@ describe('Notes Store mutations', () => {
expect(state.discussions[0].expanded).toBe(true);
});
});
+
+ describe('DISABLE_COMMENTS', () => {
+ it('should set comments disabled state', () => {
+ const state = {};
+
+ mutations.DISABLE_COMMENTS(state, true);
+
+ expect(state.commentsDisabled).toEqual(true);
+ });
+ });
});
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index a63f34b5536..f4efa450cca 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -299,6 +299,7 @@ project:
- ci_cd_settings
- import_export_upload
- repository_languages
+- pool_repository
award_emoji:
- awardable
- user
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 87b91286168..95ae7bd21ab 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -594,4 +594,24 @@ describe ApplicationSetting do
end
end
end
+
+ describe '#archive_builds_older_than' do
+ subject { setting.archive_builds_older_than }
+
+ context 'when the archive_builds_in_seconds is set' do
+ before do
+ setting.archive_builds_in_seconds = 3600
+ end
+
+ it { is_expected.to be_within(1.minute).of(1.hour.ago) }
+ end
+
+ context 'when the archive_builds_in_seconds is set' do
+ before do
+ setting.archive_builds_in_seconds = nil
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index b07f8bc98b5..4089f099fdf 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1314,6 +1314,14 @@ describe Ci::Build do
it { is_expected.not_to be_retryable }
end
+
+ context 'when build is degenerated' do
+ before do
+ build.degenerate!
+ end
+
+ it { is_expected.not_to be_retryable }
+ end
end
end
@@ -1396,6 +1404,14 @@ describe Ci::Build do
expect(subject.retries_max).to eq 0
end
end
+
+ context 'when build is degenerated' do
+ subject { create(:ci_build, :degenerated) }
+
+ it 'returns zero' do
+ expect(subject.retries_max).to eq 0
+ end
+ end
end
end
@@ -1659,6 +1675,12 @@ describe Ci::Build do
it { is_expected.to be_playable }
end
+
+ context 'when build is a manual and degenerated' do
+ subject { build_stubbed(:ci_build, :manual, :degenerated, status: :manual) }
+
+ it { is_expected.not_to be_playable }
+ end
end
context 'when build is scheduled' do
@@ -3227,4 +3249,54 @@ describe Ci::Build do
it { expect(build.deployment_status).to eq(:creating) }
end
end
+
+ describe '#degenerated?' do
+ context 'when build is degenerated' do
+ subject { create(:ci_build, :degenerated) }
+
+ it { is_expected.to be_degenerated }
+ end
+
+ context 'when build is valid' do
+ subject { create(:ci_build) }
+
+ it { is_expected.not_to be_degenerated }
+
+ context 'and becomes degenerated' do
+ before do
+ subject.degenerate!
+ end
+
+ it { is_expected.to be_degenerated }
+ end
+ end
+ end
+
+ describe '#archived?' do
+ context 'when build is degenerated' do
+ subject { create(:ci_build, :degenerated) }
+
+ it { is_expected.to be_archived }
+ end
+
+ context 'for old build' do
+ subject { create(:ci_build, created_at: 1.day.ago) }
+
+ context 'when archive_builds_in is set' do
+ before do
+ stub_application_setting(archive_builds_in_seconds: 3600)
+ end
+
+ it { is_expected.to be_archived }
+ end
+
+ context 'when archive_builds_in is not set' do
+ before do
+ stub_application_setting(archive_builds_in_seconds: nil)
+ end
+
+ it { is_expected.not_to be_archived }
+ end
+ end
+ end
end
diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb
index 919a7526803..e100af7ddc7 100644
--- a/spec/models/postgresql/replication_slot_spec.rb
+++ b/spec/models/postgresql/replication_slot_spec.rb
@@ -3,7 +3,27 @@
require 'spec_helper'
describe Postgresql::ReplicationSlot, :postgresql do
+ describe '.in_use?' do
+ it 'returns true when replication slots are present' do
+ expect(described_class).to receive(:exists?).and_return(true)
+ expect(described_class.in_use?).to be_truthy
+ end
+
+ it 'returns false when replication slots are not present' do
+ expect(described_class.in_use?).to be_falsey
+ end
+
+ it 'returns false if the existence check is invalid' do
+ expect(described_class).to receive(:exists?).and_raise(ActiveRecord::StatementInvalid.new('PG::FeatureNotSupported'))
+ expect(described_class.in_use?).to be_falsey
+ end
+ end
+
describe '.lag_too_great?' do
+ before do
+ expect(described_class).to receive(:in_use?).and_return(true)
+ end
+
it 'returns true when replication lag is too great' do
expect(described_class)
.to receive(:pluck)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d059854214f..84326724118 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -8,6 +8,7 @@ describe Project do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:namespace) }
it { is_expected.to belong_to(:creator).class_name('User') }
+ it { is_expected.to belong_to(:pool_repository) }
it { is_expected.to have_many(:users) }
it { is_expected.to have_many(:services) }
it { is_expected.to have_many(:events) }
diff --git a/spec/models/shard_spec.rb b/spec/models/shard_spec.rb
new file mode 100644
index 00000000000..83104711b55
--- /dev/null
+++ b/spec/models/shard_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literals: true
+require 'spec_helper'
+
+describe Shard do
+ describe '.populate!' do
+ it 'creates shards based on the config file' do
+ expect(described_class.all).to be_empty
+
+ stub_storage_settings(foo: {}, bar: {}, baz: {})
+
+ described_class.populate!
+
+ expect(described_class.all.map(&:name)).to match_array(%w[default foo bar baz])
+ end
+ end
+
+ describe '.by_name' do
+ let(:default_shard) { described_class.find_by(name: 'default') }
+
+ before do
+ described_class.populate!
+ end
+
+ it 'returns an existing shard' do
+ expect(described_class.by_name('default')).to eq(default_shard)
+ end
+
+ it 'creates a new shard' do
+ result = described_class.by_name('foo')
+
+ expect(result).not_to eq(default_shard)
+ expect(result.name).to eq('foo')
+ end
+
+ it 'retries if creation races' do
+ expect(described_class)
+ .to receive(:find_or_create_by)
+ .with(name: 'default')
+ .and_raise(ActiveRecord::RecordNotUnique, 'fail')
+ .once
+
+ expect(described_class)
+ .to receive(:find_or_create_by)
+ .with(name: 'default')
+ .and_call_original
+
+ expect(described_class.by_name('default')).to eq(default_shard)
+ end
+ end
+end
diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb
index 64d9d9a78b4..2898613545c 100644
--- a/spec/models/user_preference_spec.rb
+++ b/spec/models/user_preference_spec.rb
@@ -6,22 +6,43 @@ describe UserPreference do
describe '#set_notes_filter' do
let(:issuable) { build_stubbed(:issue) }
let(:user_preference) { create(:user_preference) }
- let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] }
- it 'returns updated discussion filter' do
- filter_name =
- user_preference.set_notes_filter(only_comments, issuable)
+ shared_examples 'setting system notes' do
+ it 'returns updated discussion filter' do
+ filter_name =
+ user_preference.set_notes_filter(filter, issuable)
+
+ expect(filter_name).to eq(filter)
+ end
+
+ it 'updates discussion filter for issuable class' do
+ user_preference.set_notes_filter(filter, issuable)
+
+ expect(user_preference.reload.issue_notes_filter).to eq(filter)
+ end
+ end
+
+ context 'when filter is set to all notes' do
+ let(:filter) { described_class::NOTES_FILTERS[:all_notes] }
+
+ it_behaves_like 'setting system notes'
+ end
+
+ context 'when filter is set to only comments' do
+ let(:filter) { described_class::NOTES_FILTERS[:only_comments] }
- expect(filter_name).to eq(only_comments)
+ it_behaves_like 'setting system notes'
end
- it 'updates discussion filter for issuable class' do
- user_preference.set_notes_filter(only_comments, issuable)
+ context 'when filter is set to only activity' do
+ let(:filter) { described_class::NOTES_FILTERS[:only_activity] }
- expect(user_preference.reload.issue_notes_filter).to eq(only_comments)
+ it_behaves_like 'setting system notes'
end
context 'when notes_filter parameter is invalid' do
+ let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] }
+
it 'returns the current notes filter' do
user_preference.set_notes_filter(only_comments, issuable)
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index d7992f0a4a9..676835b3880 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -267,7 +267,7 @@ describe Ci::BuildPresenter do
let(:build) { create(:ci_build, :failed, :script_failure) }
context 'when is a script or missing dependency failure' do
- let(:failure_reasons) { %w(script_failure missing_dependency_failure) }
+ let(:failure_reasons) { %w(script_failure missing_dependency_failure archived_failure) }
it 'should return false' do
failure_reasons.each do |failure_reason|
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index a6565709641..56e2a405bcd 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -478,6 +478,20 @@ module Ci
it_behaves_like 'validation is not active'
end
end
+
+ context 'when build is degenerated' do
+ let!(:pending_job) { create(:ci_build, :pending, :degenerated, pipeline: pipeline) }
+
+ subject { execute(specific_runner, {}) }
+
+ it 'does not pick the build and drops the build' do
+ expect(subject).to be_nil
+
+ pending_job.reload
+ expect(pending_job).to be_failed
+ expect(pending_job).to be_archived_failure
+ end
+ end
end
describe '#register_success' do
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index 9c9d7ad781e..95e69328080 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -34,12 +34,24 @@ shared_examples 'issuable notes filter' do
expect(user.reload.notes_filter_for(issuable)).to eq(0)
end
- it 'returns no system note' do
+ it 'returns only user comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
+ discussions = JSON.parse(response.body)
- expect(JSON.parse(response.body).count).to eq(1)
+ expect(discussions.count).to eq(1)
+ expect(discussions.first["notes"].first["system"]).to be(false)
+ end
+
+ it 'returns only activity notes' do
+ user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
+ discussions = JSON.parse(response.body)
+
+ expect(discussions.count).to eq(1)
+ expect(discussions.first["notes"].first["system"]).to be(true)
end
context 'when filter is set to "only_comments"' do