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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-05 03:08:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-05 03:08:43 +0300
commit63a7b9df2282f15217baa2512e44f06bf0f256eb (patch)
treee69a3b797054db2ef33ad6744a5bd92d07bb537a
parent9e19896fb55fe053414109dcfb6e4f3142918e08 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/snippets/components/edit.vue30
-rw-r--r--app/assets/javascripts/snippets/components/snippet_visibility_edit.vue50
-rw-r--r--app/assets/javascripts/snippets/constants.js12
-rw-r--r--app/assets/javascripts/snippets/index.js18
-rw-r--r--app/assets/javascripts/snippets/queries/snippet_visibility.query.graphql5
-rw-r--r--app/assets/javascripts/snippets/utils/blob.js15
-rw-r--r--app/models/ci/ref.rb2
-rw-r--r--app/models/merge_request_assignee.rb2
-rw-r--r--app/services/ci/destroy_expired_job_artifacts_service.rb2
-rw-r--r--app/views/shared/snippets/_form.html.haml3
-rw-r--r--changelogs/unreleased/mc-feature-remove-keep-latest-artifact-ff.yml5
-rw-r--r--changelogs/unreleased/pherlihy-master-patch-95213.yml5
-rw-r--r--db/migrate/20200826073745_add_default_to_ci_pipeline_locked.rb23
-rw-r--r--db/post_migrate/20200826121552_remove_ci_job_artifacts_locked.rb19
-rw-r--r--db/schema_migrations/202008260737451
-rw-r--r--db/schema_migrations/202008261215521
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql60
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json165
-rw-r--r--doc/ci/yaml/README.md8
-rw-r--r--lib/gitlab/ci/features.rb8
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb6
-rw-r--r--package.json2
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb4
-rw-r--r--spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap8
-rw-r--r--spec/frontend/snippets/components/edit_spec.js7
-rw-r--r--spec/frontend/snippets/components/snippet_visibility_edit_spec.js122
-rw-r--r--spec/models/ci/ref_spec.rb64
-rw-r--r--yarn.lock10
29 files changed, 524 insertions, 136 deletions
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index 3d2eaebf1cb..1a539aa0876 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -6,19 +6,21 @@ import { __, sprintf } from '~/locale';
import TitleField from '~/vue_shared/components/form/title.vue';
import { redirectTo, joinPaths } from '~/lib/utils/url_utility';
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
+import { SNIPPET_MARK_EDIT_APP_START } from '~/performance_constants';
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
import CreateSnippetMutation from '../mutations/createSnippet.mutation.graphql';
import { getSnippetMixin } from '../mixins/snippets';
import {
- SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_CREATE_MUTATION_ERROR,
SNIPPET_UPDATE_MUTATION_ERROR,
+ SNIPPET_VISIBILITY_PRIVATE,
} from '../constants';
+import defaultVisibilityQuery from '../queries/snippet_visibility.query.graphql';
+
import SnippetBlobActionsEdit from './snippet_blob_actions_edit.vue';
import SnippetVisibilityEdit from './snippet_visibility_edit.vue';
import SnippetDescriptionEdit from './snippet_description_edit.vue';
-import { SNIPPET_MARK_EDIT_APP_START } from '~/performance_constants';
export default {
components: {
@@ -31,6 +33,15 @@ export default {
GlLoadingIcon,
},
mixins: [getSnippetMixin],
+ apollo: {
+ defaultVisibility: {
+ query: defaultVisibilityQuery,
+ manual: true,
+ result({ data: { selectedLevel } }) {
+ this.selectedLevelDefault = selectedLevel;
+ },
+ },
+ },
props: {
markdownPreviewPath: {
type: String,
@@ -56,6 +67,7 @@ export default {
isUpdating: false,
newSnippet: false,
actions: [],
+ selectedLevelDefault: SNIPPET_VISIBILITY_PRIVATE,
};
},
computed: {
@@ -98,6 +110,13 @@ export default {
descriptionFieldId() {
return `${this.isProjectSnippet ? 'project' : 'personal'}_snippet_description`;
},
+ newSnippetSchema() {
+ return {
+ title: '',
+ description: '',
+ visibilityLevel: this.selectedLevelDefault,
+ };
+ },
},
beforeCreate() {
performance.mark(SNIPPET_MARK_EDIT_APP_START);
@@ -126,7 +145,7 @@ export default {
},
onNewSnippetFetched() {
this.newSnippet = true;
- this.snippet = this.$options.newSnippetSchema;
+ this.snippet = this.newSnippetSchema;
},
onExistingSnippetFetched() {
this.newSnippet = false;
@@ -184,11 +203,6 @@ export default {
this.actions = actions;
},
},
- newSnippetSchema: {
- title: '',
- description: '',
- visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
- },
};
</script>
<template>
diff --git a/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue b/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue
index 299bb8fcfad..25ad7c214b2 100644
--- a/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue
+++ b/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue
@@ -1,11 +1,8 @@
<script>
import { GlIcon, GlFormGroup, GlFormRadio, GlFormRadioGroup, GlLink } from '@gitlab/ui';
-import {
- SNIPPET_VISIBILITY,
- SNIPPET_VISIBILITY_PRIVATE,
- SNIPPET_VISIBILITY_INTERNAL,
- SNIPPET_VISIBILITY_PUBLIC,
-} from '~/snippets/constants';
+import defaultVisibilityQuery from '../queries/snippet_visibility.query.graphql';
+import { defaultSnippetVisibilityLevels } from '../utils/blob';
+import { SNIPPET_LEVELS_RESTRICTED, SNIPPET_LEVELS_DISABLED } from '~/snippets/constants';
export default {
components: {
@@ -15,6 +12,16 @@ export default {
GlFormRadioGroup,
GlLink,
},
+ apollo: {
+ defaultVisibility: {
+ query: defaultVisibilityQuery,
+ manual: true,
+ result({ data: { visibilityLevels, multipleLevelsRestricted } }) {
+ this.visibilityLevels = defaultSnippetVisibilityLevels(visibilityLevels);
+ this.multipleLevelsRestricted = multipleLevelsRestricted;
+ },
+ },
+ },
props: {
helpLink: {
type: String,
@@ -28,19 +35,17 @@ export default {
},
value: {
type: String,
- required: false,
- default: SNIPPET_VISIBILITY_PRIVATE,
+ required: true,
},
},
- computed: {
- visibilityOptions() {
- return [
- SNIPPET_VISIBILITY_PRIVATE,
- SNIPPET_VISIBILITY_INTERNAL,
- SNIPPET_VISIBILITY_PUBLIC,
- ].map(key => ({ value: key, ...SNIPPET_VISIBILITY[key] }));
- },
+ data() {
+ return {
+ visibilityLevels: [],
+ multipleLevelsRestricted: false,
+ };
},
+ SNIPPET_LEVELS_DISABLED,
+ SNIPPET_LEVELS_RESTRICTED,
};
</script>
<template>
@@ -51,10 +56,10 @@ export default {
><gl-icon :size="12" name="question"
/></gl-link>
</label>
- <gl-form-group id="visibility-level-setting">
- <gl-form-radio-group v-bind="$attrs" :checked="value" stacked v-on="$listeners">
+ <gl-form-group id="visibility-level-setting" class="gl-mb-0">
+ <gl-form-radio-group :checked="value" stacked v-bind="$attrs" v-on="$listeners">
<gl-form-radio
- v-for="option in visibilityOptions"
+ v-for="option in visibilityLevels"
:key="option.value"
:value="option.value"
class="mb-3"
@@ -71,5 +76,12 @@ export default {
</gl-form-radio>
</gl-form-radio-group>
</gl-form-group>
+
+ <div class="text-muted" data-testid="restricted-levels-info">
+ <template v-if="!visibilityLevels.length">{{ $options.SNIPPET_LEVELS_DISABLED }}</template>
+ <template v-else-if="multipleLevelsRestricted">{{
+ $options.SNIPPET_LEVELS_RESTRICTED
+ }}</template>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/snippets/constants.js b/app/assets/javascripts/snippets/constants.js
index 12b83525bf7..e75922df15f 100644
--- a/app/assets/javascripts/snippets/constants.js
+++ b/app/assets/javascripts/snippets/constants.js
@@ -33,3 +33,15 @@ export const SNIPPET_BLOB_ACTION_MOVE = 'move';
export const SNIPPET_BLOB_ACTION_DELETE = 'delete';
export const SNIPPET_MAX_BLOBS = 10;
+
+export const SNIPPET_LEVELS_MAP = {
+ 0: SNIPPET_VISIBILITY_PRIVATE,
+ 10: SNIPPET_VISIBILITY_INTERNAL,
+ 20: SNIPPET_VISIBILITY_PUBLIC,
+};
+export const SNIPPET_LEVELS_RESTRICTED = __(
+ 'Other visibility settings have been disabled by the administrator.',
+);
+export const SNIPPET_LEVELS_DISABLED = __(
+ 'Visibility settings have been disabled by the administrator.',
+);
diff --git a/app/assets/javascripts/snippets/index.js b/app/assets/javascripts/snippets/index.js
index bb5e7d6e3f0..c70ad9b95f8 100644
--- a/app/assets/javascripts/snippets/index.js
+++ b/app/assets/javascripts/snippets/index.js
@@ -5,6 +5,7 @@ import createDefaultClient from '~/lib/graphql';
import SnippetsShow from './components/show.vue';
import SnippetsEdit from './components/edit.vue';
+import { SNIPPET_LEVELS_MAP, SNIPPET_VISIBILITY_PRIVATE } from '~/snippets/constants';
Vue.use(VueApollo);
Vue.use(Translate);
@@ -18,13 +19,28 @@ function appFactory(el, Component) {
defaultClient: createDefaultClient(),
});
+ const {
+ visibilityLevels = '[]',
+ selectedLevel,
+ multipleLevelsRestricted,
+ ...restDataset
+ } = el.dataset;
+
+ apolloProvider.clients.defaultClient.cache.writeData({
+ data: {
+ visibilityLevels: JSON.parse(visibilityLevels),
+ selectedLevel: SNIPPET_LEVELS_MAP[selectedLevel] ?? SNIPPET_VISIBILITY_PRIVATE,
+ multipleLevelsRestricted: 'multipleLevelsRestricted' in el.dataset,
+ },
+ });
+
return new Vue({
el,
apolloProvider,
render(createElement) {
return createElement(Component, {
props: {
- ...el.dataset,
+ ...restDataset,
},
});
},
diff --git a/app/assets/javascripts/snippets/queries/snippet_visibility.query.graphql b/app/assets/javascripts/snippets/queries/snippet_visibility.query.graphql
new file mode 100644
index 00000000000..5bd6c131bab
--- /dev/null
+++ b/app/assets/javascripts/snippets/queries/snippet_visibility.query.graphql
@@ -0,0 +1,5 @@
+query defaultSnippetVisibility {
+ visibilityLevels @client
+ selectedLevel @client
+ multipleLevelsRestricted @client
+}
diff --git a/app/assets/javascripts/snippets/utils/blob.js b/app/assets/javascripts/snippets/utils/blob.js
index fd5ff9a3d2e..21f52671801 100644
--- a/app/assets/javascripts/snippets/utils/blob.js
+++ b/app/assets/javascripts/snippets/utils/blob.js
@@ -4,6 +4,8 @@ import {
SNIPPET_BLOB_ACTION_UPDATE,
SNIPPET_BLOB_ACTION_MOVE,
SNIPPET_BLOB_ACTION_DELETE,
+ SNIPPET_LEVELS_MAP,
+ SNIPPET_VISIBILITY,
} from '../constants';
const createLocalId = () => uniqueId('blob_local_');
@@ -64,3 +66,16 @@ export const diffAll = (blobs, origBlobs) => {
return [...deletedEntries, ...newEntries];
};
+
+export const defaultSnippetVisibilityLevels = arr => {
+ if (Array.isArray(arr)) {
+ return arr.map(l => {
+ const translatedLevel = SNIPPET_LEVELS_MAP[l];
+ return {
+ value: translatedLevel,
+ ...SNIPPET_VISIBILITY[translatedLevel],
+ };
+ });
+ }
+ return [];
+};
diff --git a/app/models/ci/ref.rb b/app/models/ci/ref.rb
index 3d8823728e7..6e9b8416c10 100644
--- a/app/models/ci/ref.rb
+++ b/app/models/ci/ref.rb
@@ -33,8 +33,6 @@ module Ci
state :still_failing, value: 5
after_transition any => [:fixed, :success] do |ci_ref|
- next unless ::Gitlab::Ci::Features.keep_latest_artifacts_for_ref_enabled?(ci_ref.project)
-
ci_ref.run_after_commit do
Ci::PipelineSuccessUnlockArtifactsWorker.perform_async(ci_ref.last_finished_pipeline_id)
end
diff --git a/app/models/merge_request_assignee.rb b/app/models/merge_request_assignee.rb
index 2ac1de4321a..73f8fe77b04 100644
--- a/app/models/merge_request_assignee.rb
+++ b/app/models/merge_request_assignee.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class MergeRequestAssignee < ApplicationRecord
- belongs_to :merge_request
+ belongs_to :merge_request, touch: true
belongs_to :assignee, class_name: "User", foreign_key: :user_id, inverse_of: :merge_request_assignees
validates :assignee, uniqueness: { scope: :merge_request_id }
diff --git a/app/services/ci/destroy_expired_job_artifacts_service.rb b/app/services/ci/destroy_expired_job_artifacts_service.rb
index 5694d031a0f..ca6e60f819a 100644
--- a/app/services/ci/destroy_expired_job_artifacts_service.rb
+++ b/app/services/ci/destroy_expired_job_artifacts_service.rb
@@ -28,7 +28,7 @@ module Ci
private
def destroy_batch(klass)
- artifact_batch = if klass == Ci::JobArtifact && Gitlab::Ci::Features.destroy_only_unlocked_expired_artifacts_enabled?
+ artifact_batch = if klass == Ci::JobArtifact
klass.expired(BATCH_SIZE).unlocked
else
klass.expired(BATCH_SIZE)
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 81277b50d13..41098b60193 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -1,5 +1,6 @@
- if Feature.enabled?(:snippets_edit_vue)
- #js-snippet-edit.snippet-form{ data: {'project_path': @snippet.project&.full_path, 'snippet-gid': @snippet.new_record? ? '' : @snippet.to_global_id, 'markdown-preview-path': preview_markdown_path(parent), 'markdown-docs-path': help_page_path('user/markdown'), 'visibility-help-link': help_page_path("public_access/public_access") } }
+ - available_visibility_levels = available_visibility_levels(@snippet)
+ #js-snippet-edit.snippet-form{ data: {'project_path': @snippet.project&.full_path, 'snippet-gid': @snippet.new_record? ? '' : @snippet.to_global_id, 'markdown-preview-path': preview_markdown_path(parent), 'markdown-docs-path': help_page_path('user/markdown'), 'visibility-help-link': help_page_path("public_access/public_access"), 'visibility_levels': available_visibility_levels, 'selected_level': snippets_selected_visibility_level(available_visibility_levels, @snippet.visibility_level), 'multiple_levels_restricted': multiple_visibility_levels_restricted? } }
- else
.snippet-form-holder
= form_for @snippet, url: url,
diff --git a/changelogs/unreleased/mc-feature-remove-keep-latest-artifact-ff.yml b/changelogs/unreleased/mc-feature-remove-keep-latest-artifact-ff.yml
new file mode 100644
index 00000000000..8b3449f3273
--- /dev/null
+++ b/changelogs/unreleased/mc-feature-remove-keep-latest-artifact-ff.yml
@@ -0,0 +1,5 @@
+---
+title: Remove keep latest artifact feature flags.
+merge_request: 40478
+author:
+type: other
diff --git a/changelogs/unreleased/pherlihy-master-patch-95213.yml b/changelogs/unreleased/pherlihy-master-patch-95213.yml
new file mode 100644
index 00000000000..8d129388d47
--- /dev/null
+++ b/changelogs/unreleased/pherlihy-master-patch-95213.yml
@@ -0,0 +1,5 @@
+---
+title: Change merge request updated_at when assignees are changed
+merge_request: 41030
+author: Patrick Herlihy
+type: fixed
diff --git a/db/migrate/20200826073745_add_default_to_ci_pipeline_locked.rb b/db/migrate/20200826073745_add_default_to_ci_pipeline_locked.rb
new file mode 100644
index 00000000000..e73963043d5
--- /dev/null
+++ b/db/migrate/20200826073745_add_default_to_ci_pipeline_locked.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddDefaultToCiPipelineLocked < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ ARTIFACTS_LOCKED = 1
+ UNLOCKED = 0
+
+ def up
+ with_lock_retries do
+ change_column_default :ci_pipelines, :locked, ARTIFACTS_LOCKED
+ end
+ end
+
+ def down
+ with_lock_retries do
+ change_column_default :ci_pipelines, :locked, UNLOCKED
+ end
+ end
+end
diff --git a/db/post_migrate/20200826121552_remove_ci_job_artifacts_locked.rb b/db/post_migrate/20200826121552_remove_ci_job_artifacts_locked.rb
new file mode 100644
index 00000000000..21c7acca7ac
--- /dev/null
+++ b/db/post_migrate/20200826121552_remove_ci_job_artifacts_locked.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveCiJobArtifactsLocked < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ remove_column :ci_job_artifacts, :locked
+ end
+ end
+
+ def down
+ with_lock_retries do
+ add_column :ci_job_artifacts, :locked, :boolean
+ end
+ end
+end
diff --git a/db/schema_migrations/20200826073745 b/db/schema_migrations/20200826073745
new file mode 100644
index 00000000000..0200c7bc66a
--- /dev/null
+++ b/db/schema_migrations/20200826073745
@@ -0,0 +1 @@
+d3b15469120ed213363de33a4b268ed71a710c40f02d4a669edf2c5412907209 \ No newline at end of file
diff --git a/db/schema_migrations/20200826121552 b/db/schema_migrations/20200826121552
new file mode 100644
index 00000000000..e794680240a
--- /dev/null
+++ b/db/schema_migrations/20200826121552
@@ -0,0 +1 @@
+8667c30042b19428b97e0995821c183e69f73394503c83a55ba7bd870df7c3e8 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 754fbac8dd1..826ffe6e9f5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10083,7 +10083,6 @@ CREATE TABLE public.ci_job_artifacts (
file_sha256 bytea,
file_format smallint,
file_location smallint,
- locked boolean,
CONSTRAINT check_27f0f6dbab CHECK ((file_store IS NOT NULL))
);
@@ -10267,7 +10266,7 @@ CREATE TABLE public.ci_pipelines (
target_sha bytea,
external_pull_request_id bigint,
ci_ref_id bigint,
- locked smallint DEFAULT 0 NOT NULL,
+ locked smallint DEFAULT 1 NOT NULL,
CONSTRAINT check_d7e99a025e CHECK ((lock_version IS NOT NULL))
);
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index e1321deea21..c49ff1a5609 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -1716,6 +1716,26 @@ type ClusterAgent {
}
"""
+The connection type for ClusterAgent.
+"""
+type ClusterAgentConnection {
+ """
+ A list of edges.
+ """
+ edges: [ClusterAgentEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [ClusterAgent]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
Autogenerated input type of ClusterAgentDelete
"""
input ClusterAgentDeleteInput {
@@ -1745,6 +1765,21 @@ type ClusterAgentDeletePayload {
errors: [String!]!
}
+"""
+An edge in a connection.
+"""
+type ClusterAgentEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: ClusterAgent
+}
+
type ClusterAgentToken {
"""
Cluster agent this token is associated with
@@ -11319,6 +11354,31 @@ type Project {
): BoardConnection
"""
+ Cluster agents associated with the project
+ """
+ clusterAgents(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): ClusterAgentConnection
+
+ """
Compliance frameworks associated with the project
"""
complianceFrameworks(
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 36bc3f6e83d..e7beae1b01a 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -4682,6 +4682,73 @@
"possibleTypes": null
},
{
+ "kind": "OBJECT",
+ "name": "ClusterAgentConnection",
+ "description": "The connection type for ClusterAgent.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ClusterAgentEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "ClusterAgent",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "INPUT_OBJECT",
"name": "ClusterAgentDeleteInput",
"description": "Autogenerated input type of ClusterAgentDelete",
@@ -4771,6 +4838,51 @@
},
{
"kind": "OBJECT",
+ "name": "ClusterAgentEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ClusterAgent",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "ClusterAgentToken",
"description": null,
"fields": [
@@ -33856,6 +33968,59 @@
"deprecationReason": null
},
{
+ "name": "clusterAgents",
+ "description": "Cluster agents associated with the project",
+ "args": [
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "ClusterAgentConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "complianceFrameworks",
"description": "Compliance frameworks associated with the project",
"args": [
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index d18f081dbad..f93d021daf5 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -3289,10 +3289,10 @@ job:
```
NOTE: **Note:**
-Since [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/16267), the latest
-artifacts for refs can be locked against deletion, and kept regardless of the expiry time. This feature is disabled
-by default and is not ready for production use. It can be enabled for testing by
-enabling the `:keep_latest_artifact_for_ref` and `:destroy_only_unlocked_expired_artifacts` [feature flags](../../administration/feature_flags.md).
+The latest artifacts for refs are locked against deletion, and kept regardless of
+the expiry time. [Introduced in](https://gitlab.com/gitlab-org/gitlab/-/issues/16267)
+GitLab 13.0 behind a disabled feature flag, and [made the default behavior](https://gitlab.com/gitlab-org/gitlab/-/issues/229936)
+in GitLab 13.4.
#### `artifacts:reports`
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index ab70ba5e17e..a03206c1358 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -40,14 +40,6 @@ module Gitlab
::Feature.enabled?(:ci_raise_job_rules_without_workflow_rules_warning, default_enabled: true)
end
- def self.keep_latest_artifacts_for_ref_enabled?(project)
- ::Feature.enabled?(:keep_latest_artifacts_for_ref, project, default_enabled: true)
- end
-
- def self.destroy_only_unlocked_expired_artifacts_enabled?
- ::Feature.enabled?(:destroy_only_unlocked_expired_artifacts, default_enabled: true)
- end
-
def self.bulk_insert_on_create?(project)
::Feature.enabled?(:ci_bulk_insert_on_create, project, default_enabled: true)
end
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 4190c40eb66..9662209f88e 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -20,11 +20,7 @@ module Gitlab
pipeline_schedule: @command.schedule,
merge_request: @command.merge_request,
external_pull_request: @command.external_pull_request,
- variables_attributes: Array(@command.variables_attributes),
- # This should be removed and set on the database column default
- # level when the keep_latest_artifacts_for_ref feature flag is
- # removed.
- locked: ::Gitlab::Ci::Features.keep_latest_artifacts_for_ref_enabled?(@command.project) ? :artifacts_locked : :unlocked
+ variables_attributes: Array(@command.variables_attributes)
)
end
diff --git a/package.json b/package.json
index ce902a96581..13620f680cb 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.161.0",
- "@gitlab/ui": "20.18.1",
+ "@gitlab/ui": "20.18.3",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.22.3",
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
index b6cd97dcc5a..35446b125ce 100644
--- a/spec/features/merge_request/user_assigns_themselves_spec.rb
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -21,6 +21,10 @@ RSpec.describe 'Merge request > User assigns themselves' do
expect(page).to have_content '2 issues have been assigned to you'
end
+ it 'updates updated_by', :js do
+ expect { click_button 'assign yourself' }.to change { merge_request.reload.updated_at }
+ end
+
it 'returns user to the merge request', :js do
click_link 'Assign yourself to these issues'
diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap
index be75a5bfbdc..8446f0f50c4 100644
--- a/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap
+++ b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap
@@ -20,6 +20,7 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
</label>
<gl-form-group-stub
+ class="gl-mb-0"
id="visibility-level-setting"
>
<gl-form-radio-group-stub
@@ -90,5 +91,12 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
</gl-form-radio-stub>
</gl-form-radio-group-stub>
</gl-form-group-stub>
+
+ <div
+ class="text-muted"
+ data-testid="restricted-levels-info"
+ >
+ <!---->
+ </div>
</div>
`;
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 33152591d42..b6abb9f389a 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -102,6 +102,13 @@ describe('Snippet Edit app', () => {
markdownDocsPath: 'http://docs.foo.bar',
...props,
},
+ data() {
+ return {
+ snippet: {
+ visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
+ },
+ };
+ },
});
}
diff --git a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
index 4ba3e906fc3..3919e4d7993 100644
--- a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
@@ -1,31 +1,55 @@
import { GlFormRadio, GlIcon, GlFormRadioGroup, GlLink } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
+import { defaultSnippetVisibilityLevels } from '~/snippets/utils/blob';
import {
SNIPPET_VISIBILITY,
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_VISIBILITY_INTERNAL,
SNIPPET_VISIBILITY_PUBLIC,
+ SNIPPET_LEVELS_RESTRICTED,
+ SNIPPET_LEVELS_DISABLED,
} from '~/snippets/constants';
describe('Snippet Visibility Edit component', () => {
let wrapper;
const defaultHelpLink = '/foo/bar';
const defaultVisibilityLevel = 'private';
-
- function createComponent(propsData = {}, deep = false) {
+ const defaultVisibility = defaultSnippetVisibilityLevels([0, 10, 20]);
+
+ function createComponent({
+ propsData = {},
+ visibilityLevels = defaultVisibility,
+ multipleLevelsRestricted = false,
+ deep = false,
+ } = {}) {
const method = deep ? mount : shallowMount;
+ const $apollo = {
+ queries: {
+ defaultVisibility: {
+ loading: false,
+ },
+ },
+ };
+
wrapper = method.call(this, SnippetVisibilityEdit, {
+ mock: { $apollo },
propsData: {
helpLink: defaultHelpLink,
isProjectSnippet: false,
value: defaultVisibilityLevel,
...propsData,
},
+ data() {
+ return {
+ visibilityLevels,
+ multipleLevelsRestricted,
+ };
+ },
});
}
- const findLabel = () => wrapper.find('label');
+ const findLink = () => wrapper.find('label').find(GlLink);
const findRadios = () => wrapper.find(GlFormRadioGroup).findAll(GlFormRadio);
const findRadiosData = () =>
findRadios().wrappers.map(x => {
@@ -47,60 +71,84 @@ describe('Snippet Visibility Edit component', () => {
expect(wrapper.element).toMatchSnapshot();
});
- it('renders visibility options', () => {
- createComponent({}, true);
+ it('renders label help link', () => {
+ createComponent();
+
+ expect(findLink().attributes('href')).toBe(defaultHelpLink);
+ });
+
+ it('when helpLink is not defined, does not render label help link', () => {
+ createComponent({ propsData: { helpLink: null } });
- expect(findRadiosData()).toEqual([
- {
+ expect(findLink().exists()).toBe(false);
+ });
+
+ describe('Visibility options', () => {
+ const findRestrictedInfo = () => wrapper.find('[data-testid="restricted-levels-info"]');
+ const RESULTING_OPTIONS = {
+ 0: {
value: SNIPPET_VISIBILITY_PRIVATE,
icon: SNIPPET_VISIBILITY.private.icon,
text: SNIPPET_VISIBILITY.private.label,
description: SNIPPET_VISIBILITY.private.description,
},
- {
+ 10: {
value: SNIPPET_VISIBILITY_INTERNAL,
icon: SNIPPET_VISIBILITY.internal.icon,
text: SNIPPET_VISIBILITY.internal.label,
description: SNIPPET_VISIBILITY.internal.description,
},
- {
+ 20: {
value: SNIPPET_VISIBILITY_PUBLIC,
icon: SNIPPET_VISIBILITY.public.icon,
text: SNIPPET_VISIBILITY.public.label,
description: SNIPPET_VISIBILITY.public.description,
},
- ]);
- });
-
- it('when project snippet, renders special private description', () => {
- createComponent({ isProjectSnippet: true }, true);
+ };
- expect(findRadiosData()[0]).toEqual({
- value: SNIPPET_VISIBILITY_PRIVATE,
- icon: SNIPPET_VISIBILITY.private.icon,
- text: SNIPPET_VISIBILITY.private.label,
- description: SNIPPET_VISIBILITY.private.description_project,
+ it.each`
+ levels | resultOptions
+ ${undefined} | ${[]}
+ ${''} | ${[]}
+ ${[]} | ${[]}
+ ${[0]} | ${[RESULTING_OPTIONS[0]]}
+ ${[0, 10]} | ${[RESULTING_OPTIONS[0], RESULTING_OPTIONS[10]]}
+ ${[0, 10, 20]} | ${[RESULTING_OPTIONS[0], RESULTING_OPTIONS[10], RESULTING_OPTIONS[20]]}
+ ${[0, 20]} | ${[RESULTING_OPTIONS[0], RESULTING_OPTIONS[20]]}
+ ${[10, 20]} | ${[RESULTING_OPTIONS[10], RESULTING_OPTIONS[20]]}
+ `('renders correct visibility options for $levels', ({ levels, resultOptions }) => {
+ createComponent({ visibilityLevels: defaultSnippetVisibilityLevels(levels), deep: true });
+ expect(findRadiosData()).toEqual(resultOptions);
});
- });
- it('renders label help link', () => {
- createComponent();
-
- expect(
- findLabel()
- .find(GlLink)
- .attributes('href'),
- ).toBe(defaultHelpLink);
- });
+ it.each`
+ levels | levelsRestricted | resultText
+ ${[]} | ${false} | ${SNIPPET_LEVELS_DISABLED}
+ ${[]} | ${true} | ${SNIPPET_LEVELS_DISABLED}
+ ${[0]} | ${true} | ${SNIPPET_LEVELS_RESTRICTED}
+ ${[0]} | ${false} | ${''}
+ ${[0, 10, 20]} | ${false} | ${''}
+ `(
+ 'renders correct information about restricted visibility levels for $levels',
+ ({ levels, levelsRestricted, resultText }) => {
+ createComponent({
+ visibilityLevels: defaultSnippetVisibilityLevels(levels),
+ multipleLevelsRestricted: levelsRestricted,
+ });
+ expect(findRestrictedInfo().text()).toBe(resultText);
+ },
+ );
- it('when helpLink is not defined, does not render label help link', () => {
- createComponent({ helpLink: null });
+ it('when project snippet, renders special private description', () => {
+ createComponent({ propsData: { isProjectSnippet: true }, deep: true });
- expect(
- findLabel()
- .find(GlLink)
- .exists(),
- ).toBe(false);
+ expect(findRadiosData()[0]).toEqual({
+ value: SNIPPET_VISIBILITY_PRIVATE,
+ icon: SNIPPET_VISIBILITY.private.icon,
+ text: SNIPPET_VISIBILITY.private.label,
+ description: SNIPPET_VISIBILITY.private.description_project,
+ });
+ });
});
});
@@ -108,7 +156,7 @@ describe('Snippet Visibility Edit component', () => {
it('pre-selects correct option in the list', () => {
const value = SNIPPET_VISIBILITY_INTERNAL;
- createComponent({ value });
+ createComponent({ propsData: { value } });
expect(wrapper.find(GlFormRadioGroup).attributes('checked')).toBe(value);
});
diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb
index c82c7cb85fa..c9069d82e07 100644
--- a/spec/models/ci/ref_spec.rb
+++ b/spec/models/ci/ref_spec.rb
@@ -16,51 +16,33 @@ RSpec.describe Ci::Ref do
stub_const('Ci::PipelineSuccessUnlockArtifactsWorker', unlock_artifacts_worker_spy)
end
- context 'when keep latest artifact feature is enabled' do
- before do
- stub_feature_flags(keep_latest_artifacts_for_ref: true)
- end
-
- where(:initial_state, :action, :count) do
- :unknown | :succeed! | 1
- :unknown | :do_fail! | 0
- :success | :succeed! | 1
- :success | :do_fail! | 0
- :failed | :succeed! | 1
- :failed | :do_fail! | 0
- :fixed | :succeed! | 1
- :fixed | :do_fail! | 0
- :broken | :succeed! | 1
- :broken | :do_fail! | 0
- :still_failing | :succeed | 1
- :still_failing | :do_fail | 0
- end
-
- with_them do
- context "when transitioning states" do
- before do
- status_value = Ci::Ref.state_machines[:status].states[initial_state].value
- ci_ref.update!(status: status_value)
- end
-
- it 'calls unlock artifacts service' do
- ci_ref.send(action)
-
- expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times
- end
- end
- end
+ where(:initial_state, :action, :count) do
+ :unknown | :succeed! | 1
+ :unknown | :do_fail! | 0
+ :success | :succeed! | 1
+ :success | :do_fail! | 0
+ :failed | :succeed! | 1
+ :failed | :do_fail! | 0
+ :fixed | :succeed! | 1
+ :fixed | :do_fail! | 0
+ :broken | :succeed! | 1
+ :broken | :do_fail! | 0
+ :still_failing | :succeed | 1
+ :still_failing | :do_fail | 0
end
- context 'when keep latest artifact feature is not enabled' do
- before do
- stub_feature_flags(keep_latest_artifacts_for_ref: false)
- end
+ with_them do
+ context "when transitioning states" do
+ before do
+ status_value = Ci::Ref.state_machines[:status].states[initial_state].value
+ ci_ref.update!(status: status_value)
+ end
- it 'does not call unlock artifacts service' do
- ci_ref.succeed!
+ it 'calls unlock artifacts service' do
+ ci_ref.send(action)
- expect(unlock_artifacts_worker_spy).not_to have_received(:perform_async)
+ expect(unlock_artifacts_worker_spy).to have_received(:perform_async).exactly(count).times
+ end
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 74b63faa243..ef53ffb3643 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.161.0.tgz#661e8d19862dfba0e4c558e2eb6d64b402c1453e"
integrity sha512-qsbboEICn08ZoEoAX/TuYygsFaXlzsCY+CfmdOzqvJbOdfHhVXmrJBxd2hP2qqjTZm2PkbRRmn+03+ce1jvatQ==
-"@gitlab/ui@20.18.1":
- version "20.18.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.18.1.tgz#c8a1e0830b63c056999b9417a499677fd46659af"
- integrity sha512-WbLBP6Ni8YxKqlKOZChmedc8uS7MRm5CYg/k3mRUELydF/LoW4/M0CsKwgplW4OJfEQRJr8bvmjiTLAyKAky4g==
+"@gitlab/ui@20.18.3":
+ version "20.18.3"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.18.3.tgz#14b1d6571756fbef7966d6658f56ca213ded15e8"
+ integrity sha512-yeXc40J8ifXm1aApuNNjEOZraDexYAyKZNRa05rLPXPppLnwZtx4Io5z9QiskN6fMiL648EgzC8JW576qtByCw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
@@ -861,7 +861,7 @@
echarts "^4.2.1"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
- lodash "^4.17.14"
+ lodash "^4.17.20"
portal-vue "^2.1.6"
resize-observer-polyfill "^1.5.1"
url-search-params-polyfill "^5.0.0"