summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-16 21:07:25 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-16 21:07:25 +0000
commit32cfd14a94117d1e56524727e7d1b649493f5790 (patch)
tree9de87db328f7a1e83e0e195a5e16aad78cd2b24e
parentb6b701cf9d08253d7c6f1e7500a09b1e373e18e3 (diff)
downloadgitlab-ce-32cfd14a94117d1e56524727e7d1b649493f5790.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index.vue40
-rw-r--r--app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/translations.js6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue40
-rw-r--r--app/assets/javascripts/pipelines/pipelines_index.js2
-rw-r--r--app/helpers/ci/pipelines_helper.rb9
-rw-r--r--data/removals/16_0/16-0-source-code-routes.yml36
-rw-r--r--doc/development/merge_request_concepts/diffs/development.md17
-rw-r--r--doc/development/sec/cyclonedx_property_taxonomy.md (renamed from doc/development/sec/CycloneDX_property_taxonomy.md)0
-rw-r--r--doc/update/removals.md16
-rw-r--r--doc/user/profile/comment_templates.md10
-rw-r--r--doc/user/profile/img/saved_replies_dropdown_v15_10.pngbin23623 -> 0 bytes
-rw-r--r--doc/user/profile/img/saved_replies_dropdown_v16_0.pngbin0 -> 16149 bytes
-rw-r--r--lib/gitlab/github_gists_import/importer/gist_importer.rb51
-rw-r--r--lib/gitlab/github_gists_import/representation/gist.rb4
-rw-r--r--locale/gitlab.pot12
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rwxr-xr-xscripts/lint-doc.sh31
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb16
-rw-r--r--spec/frontend/issues/list/mock_data.js16
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index_spec.js4
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js65
-rw-r--r--spec/helpers/ci/pipelines_helper_spec.rb26
-rw-r--r--spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb107
26 files changed, 431 insertions, 87 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index a52372a7bb4..8862302803c 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -42,7 +42,7 @@ review-docs-cleanup:
docs-lint links:
extends:
- .docs:rules:docs-lint
- image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-html:alpine-3.17-ruby-3.2.1-f53af000
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-html:alpine-3.17-ruby-3.2.2-c24946ab
stage: lint
needs: []
script:
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 2ed30ba6a3d..e5656a1974e 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-17db4a085675986bc5a9728bef9dde19a80bb7eb
+161d11edce6a478d5186ec2c92d95d1de0f93a01
diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index.vue b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index.vue
index 66f94c6bee5..b543169d501 100644
--- a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index.vue
+++ b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index.vue
@@ -51,27 +51,29 @@ export default {
</script>
<template>
- <div v-if="hasExperiments">
+ <div>
<model-experiments-header :page-title="$options.i18n.TITLE_LABEL" />
- <gl-table-lite :items="tableItems" :fields="$options.tableFields">
- <template #cell(nameColumn)="data">
- <gl-link :href="data.value.path">
- {{ data.value.name }}
- </gl-link>
- </template>
- </gl-table-lite>
+ <template v-if="hasExperiments">
+ <gl-table-lite :items="tableItems" :fields="$options.tableFields">
+ <template #cell(nameColumn)="data">
+ <gl-link :href="data.value.path">
+ {{ data.value.name }}
+ </gl-link>
+ </template>
+ </gl-table-lite>
- <pagination v-if="hasExperiments" v-bind="pageInfo" />
- </div>
+ <pagination v-if="hasExperiments" v-bind="pageInfo" />
+ </template>
- <gl-empty-state
- v-else
- :title="$options.i18n.EMPTY_STATE_TITLE_LABEL"
- :primary-button-text="$options.i18n.CREATE_NEW_LABEL"
- :primary-button-link="$options.constants.CREATE_EXPERIMENT_HELP_PATH"
- :svg-path="emptyStateSvgPath"
- :description="$options.i18n.EMPTY_STATE_DESCRIPTION_LABEL"
- class="gl-py-8"
- />
+ <gl-empty-state
+ v-else
+ :title="$options.i18n.EMPTY_STATE_TITLE_LABEL"
+ :primary-button-text="$options.i18n.CREATE_NEW_LABEL"
+ :primary-button-link="$options.constants.CREATE_EXPERIMENT_HELP_PATH"
+ :svg-path="emptyStateSvgPath"
+ :description="$options.i18n.EMPTY_STATE_DESCRIPTION_LABEL"
+ class="gl-py-8"
+ />
+ </div>
</template>
diff --git a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/translations.js b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/translations.js
index e954c054cf5..f556197633b 100644
--- a/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/translations.js
+++ b/app/assets/javascripts/ml/experiment_tracking/routes/experiments/index/translations.js
@@ -4,8 +4,10 @@ export const TITLE_LABEL = s__('MlExperimentTracking|Model experiments');
export const CREATE_NEW_LABEL = s__('MlExperimentTracking|Create a new experiment');
-export const EMPTY_STATE_TITLE_LABEL = s__('MlExperimentTracking|No experiments');
+export const EMPTY_STATE_TITLE_LABEL = s__(
+ 'MlExperimentTracking|Get started with model experiments!',
+);
export const EMPTY_STATE_DESCRIPTION_LABEL = s__(
- 'MlExperimentTracking|There are no logged experiments for this project. Create a new experiment using the MLflow client.',
+ 'MlExperimentTracking|Experiments keep track of comparable model candidates, and determine which parameters provides the best performance. Create experiments using the MLflow client',
);
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
index bcbf655a737..4934df0adc1 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue
@@ -1,12 +1,15 @@
<script>
import { GlEmptyState, GlIcon, GlLoadingIcon, GlCollapsibleListbox } from '@gitlab/ui';
import { isEqual } from 'lodash';
+import * as Sentry from '@sentry/browser';
import { createAlert, VARIANT_INFO, VARIANT_WARNING } from '~/alert';
import { getParameterByName } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import Tracking from '~/tracking';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import { isLoggedIn } from '~/lib/utils/common_utils';
+import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
import {
ANY_TRIGGER_AUTHOR,
RAW_TEXT_WARNING,
@@ -113,6 +116,11 @@ export default {
required: false,
default: null,
},
+ defaultVisibilityPipelineIdType: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -123,7 +131,7 @@ export default {
page: getParameterByName('page') || '1',
requestData: {},
isResetCacheButtonLoading: false,
- selectedPipelineKeyOption: this.$options.PipelineKeyOptions[0],
+ visibilityPipelineIdType: this.defaultVisibilityPipelineIdType,
};
},
stateMap: {
@@ -232,6 +240,12 @@ export default {
validatedParams() {
return validateParams(this.params);
},
+ selectedPipelineKeyOption() {
+ return (
+ this.$options.PipelineKeyOptions.find((e) => this.visibilityPipelineIdType === e.value) ||
+ this.$options.PipelineKeyOptions[0]
+ );
+ },
},
created() {
this.service = new PipelinesService(this.endpoint);
@@ -317,8 +331,26 @@ export default {
this.updateContent({ ...this.requestData, page: '1' });
},
- changeVisibilityPipelineID(val) {
- this.selectedPipelineKeyOption = PipelineKeyOptions.find((e) => val === e.value);
+ changeVisibilityPipelineIDType(idType) {
+ this.visibilityPipelineIdType = idType;
+ this.saveVisibilityPipelineIDType(idType);
+ },
+ saveVisibilityPipelineIDType(idType) {
+ if (!isLoggedIn()) return;
+
+ this.$apollo
+ .mutate({
+ mutation: setSortPreferenceMutation,
+ variables: { input: { visibilityPipelineIdType: idType.toUpperCase() } },
+ })
+ .then(({ data }) => {
+ if (data.userPreferencesUpdate.errors.length) {
+ throw new Error(data.userPreferencesUpdate.errors);
+ }
+ })
+ .catch((error) => {
+ Sentry.captureException(error);
+ });
},
},
};
@@ -362,7 +394,7 @@ export default {
data-testid="pipeline-key-collapsible-box"
:toggle-text="selectedPipelineKeyOption.text"
:items="$options.PipelineKeyOptions"
- @select="changeVisibilityPipelineID"
+ @select="changeVisibilityPipelineIDType"
/>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/pipelines_index.js b/app/assets/javascripts/pipelines/pipelines_index.js
index 49e2e1644e2..20fd0915e28 100644
--- a/app/assets/javascripts/pipelines/pipelines_index.js
+++ b/app/assets/javascripts/pipelines/pipelines_index.js
@@ -48,6 +48,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
iosRunnersAvailable,
registrationToken,
fullPath,
+ visibilityPipelineIdType,
} = el.dataset;
return new Vue({
@@ -91,6 +92,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
defaultBranchName,
params: JSON.parse(params),
registrationToken,
+ defaultVisibilityPipelineIdType: visibilityPipelineIdType,
},
});
},
diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb
index 6b15f0c9e20..897367efdb3 100644
--- a/app/helpers/ci/pipelines_helper.rb
+++ b/app/helpers/ci/pipelines_helper.rb
@@ -101,7 +101,8 @@ module Ci
has_gitlab_ci: has_gitlab_ci?(project).to_s,
pipeline_editor_path: can?(current_user, :create_pipeline, project) && project_ci_pipeline_editor_path(project),
suggested_ci_templates: suggested_ci_templates.to_json,
- full_path: project.full_path
+ full_path: project.full_path,
+ visibility_pipeline_id_type: visibility_pipeline_id_type
}
experiment(:ios_specific_templates, actor: current_user, project: project, sticky_to: project) do |e|
@@ -114,6 +115,12 @@ module Ci
data
end
+ def visibility_pipeline_id_type
+ return 'id' unless current_user.present?
+
+ current_user.user_preference.visibility_pipeline_id_type
+ end
+
private
def warning_markdown(pipeline)
diff --git a/data/removals/16_0/16-0-source-code-routes.yml b/data/removals/16_0/16-0-source-code-routes.yml
new file mode 100644
index 00000000000..d45d15e2e00
--- /dev/null
+++ b/data/removals/16_0/16-0-source-code-routes.yml
@@ -0,0 +1,36 @@
+# This is a template for announcing a feature removal or other important change.
+#
+# Please refer to the deprecation guidelines to confirm your understanding of GitLab's definitions.
+# https://docs.gitlab.com/ee/development/deprecation_guidelines/#terminology
+#
+# If this is a breaking change, it must happen in a major release.
+#
+# For more information please refer to the handbook documentation here:
+# https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-and-other-planned-breaking-change-announcements
+#
+# Please delete this line and above before submitting your merge request.
+#
+# REQUIRED FIELDS
+#
+- title: "Legacy routes removed" # (required) Clearly explain the change. For example, "The `confidential` field for a `Note` is removed" or "CI/CD job names are limited to 250 characters."
+ announcement_milestone: "15.9" # (required) The milestone when this feature was deprecated.
+ removal_milestone: "16.0" # (required) The milestone when this feature is being removed.
+ breaking_change: true # (required) Change to false if this is not a breaking change.
+ reporter: tlinz # (required) GitLab username of the person reporting the removal
+ stage: create # (required) String value of the stage that the feature was created in. e.g., Growth
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/214217 # (required) Link to the deprecation issue in GitLab
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ GitLab 16.0 removes legacy URLs from the GitLab application.
+
+ When subgroups were introduced in GitLab 9.0, a `/-/` delimiter was added to URLs to signify the end of a group path. All GitLab URLs now use this delimiter for project, group, and instance level features.
+
+ URLs that do not use the `/-/` delimiter are planned for removal in GitLab 16.0. For the full list of these URLs, along with their replacements, see [issue 28848](https://gitlab.com/gitlab-org/gitlab/-/issues/28848#release-notes).
+
+ Update any scripts or bookmarks that reference the legacy URLs. GitLab APIs are not affected by this change.
+#
+# OPTIONAL FIELDS
+#
+ tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
+ documentation_url: https://gitlab.com/gitlab-org/gitlab/-/issues/28848#release-notes # (optional) This is a link to the current documentation page
+ image_url: # (optional) This is a link to a thumbnail image depicting the feature
+ video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
diff --git a/doc/development/merge_request_concepts/diffs/development.md b/doc/development/merge_request_concepts/diffs/development.md
index 1c22eff34db..af41344ee81 100644
--- a/doc/development/merge_request_concepts/diffs/development.md
+++ b/doc/development/merge_request_concepts/diffs/development.md
@@ -119,6 +119,23 @@ relationship to the change, such as:
- Its ordering in the diff.
- The raw diff output itself.
+#### External diff storage
+
+By default, diff data of a `MergeRequestDiffFile` is stored in `diff` column in
+the `merge_request_diff_files` table. On some installations, the table can grow
+too large, so they're configured to store diffs on external storage to save space.
+To configure it, see [Merge request diffs storage](../../../administration/merge_request_diffs.md).
+
+When configured to use external storage:
+
+- The `diff` column in the database is left `NULL`.
+- The associated `MergeRequestDiff` record sets the `stored_externally` attribute
+ to `true` on creation of `MergeRequestDiff`.
+
+A cron job named `ScheduleMigrateExternalDiffsWorker` is also scheduled at
+minute 15 of every hour. This migrates `diff` that are still stored in the
+database to external storage.
+
### `MergeRequestDiffDetail`
`MergeRequestDiffDetail` is defined in `app/models/merge_request_diff_detail.rb`.
diff --git a/doc/development/sec/CycloneDX_property_taxonomy.md b/doc/development/sec/cyclonedx_property_taxonomy.md
index 6d09529a194..6d09529a194 100644
--- a/doc/development/sec/CycloneDX_property_taxonomy.md
+++ b/doc/development/sec/cyclonedx_property_taxonomy.md
diff --git a/doc/update/removals.md b/doc/update/removals.md
index bce61b36349..5bd6d306fcc 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -316,6 +316,22 @@ method.
Before upgrading to GitLab 16.0, administrators must migrate to the new single configuration structure. For
instructions, see [Praefect - Omnibus GitLab configuration structure change](https://docs.gitlab.com/ee/update/#praefect-omnibus-gitlab-configuration-structure-change).
+### Legacy routes removed
+
+<div class="deprecation-notes">
+- Announced in: GitLab <span class="milestone">15.9</span>
+- This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). Review the details carefully before upgrading.
+- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/214217).
+</div>
+
+GitLab 16.0 removes legacy URLs from the GitLab application.
+
+When subgroups were introduced in GitLab 9.0, a `/-/` delimiter was added to URLs to signify the end of a group path. All GitLab URLs now use this delimiter for project, group, and instance level features.
+
+URLs that do not use the `/-/` delimiter are planned for removal in GitLab 16.0. For the full list of these URLs, along with their replacements, see [issue 28848](https://gitlab.com/gitlab-org/gitlab/-/issues/28848#release-notes).
+
+Update any scripts or bookmarks that reference the legacy URLs. GitLab APIs are not affected by this change.
+
### License-Check and the Policies tab on the License Compliance page
<div class="deprecation-notes">
diff --git a/doc/user/profile/comment_templates.md b/doc/user/profile/comment_templates.md
index 8a94aff44fe..fed12454663 100644
--- a/doc/user/profile/comment_templates.md
+++ b/doc/user/profile/comment_templates.md
@@ -25,7 +25,7 @@ With comment templates, create and reuse text for any text area in:
Comment templates can be small, like approving a merge request and unassigning yourself from it,
or large, like chunks of boilerplate text you use frequently:
-![Comment templates dropdown list](img/saved_replies_dropdown_v15_10.png)
+![Comment templates dropdown list](img/saved_replies_dropdown_v16_0.png)
For more information about the rollout plan for this feature, see [issue 352956](https://gitlab.com/gitlab-org/gitlab/-/issues/352956).
@@ -33,7 +33,7 @@ For more information about the rollout plan for this feature, see [issue 352956]
To include the text of a comment template in your comment:
-1. In the editor toolbar for your comment, select **Comment templates** (**{symlink}**).
+1. In the editor toolbar for your comment, select **Comment templates** (**{comment-lines}**).
1. Select your desired comment template.
## Create comment templates
@@ -42,7 +42,7 @@ To create a comment template for future use:
1. On the top bar, in the upper-right corner, select your avatar.
1. From the dropdown list, select **Preferences**.
-1. On the left sidebar, select **Comment templates** (**{symlink}**).
+1. On the left sidebar, select **Comment templates** (**{comment-lines}**).
1. Provide a **Name** for your comment template.
1. Enter the **Content** of your reply. You can use any formatting you use in
other GitLab text areas.
@@ -54,7 +54,7 @@ To go to your comment templates:
1. On the top bar, in the upper-right corner, select your avatar.
1. From the dropdown list, select **Preferences**.
-1. On the left sidebar, select **Comment templates** (**{symlink}**).
+1. On the left sidebar, select **Comment templates** (**{comment-lines}**).
1. Scroll to **My comment templates**.
## Edit or delete comment templates
@@ -63,7 +63,7 @@ To edit or delete a previously comment template:
1. On the top bar, in the upper-right corner, select your avatar.
1. From the dropdown list, select **Preferences**.
-1. On the left sidebar, select **Comment templates** (**{symlink}**).
+1. On the left sidebar, select **Comment templates** (**{comment-lines}**).
1. Scroll to **My comment templates**, and identify the comment template you want to edit.
1. To edit, select **Edit** (**{pencil}**).
1. To delete, select **Delete** (**{remove}**), then select **Delete** again from the modal window.
diff --git a/doc/user/profile/img/saved_replies_dropdown_v15_10.png b/doc/user/profile/img/saved_replies_dropdown_v15_10.png
deleted file mode 100644
index 50313f71f4a..00000000000
--- a/doc/user/profile/img/saved_replies_dropdown_v15_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/profile/img/saved_replies_dropdown_v16_0.png b/doc/user/profile/img/saved_replies_dropdown_v16_0.png
new file mode 100644
index 00000000000..4608484a496
--- /dev/null
+++ b/doc/user/profile/img/saved_replies_dropdown_v16_0.png
Binary files differ
diff --git a/lib/gitlab/github_gists_import/importer/gist_importer.rb b/lib/gitlab/github_gists_import/importer/gist_importer.rb
index 4018f425e7c..71dfe5e2aa5 100644
--- a/lib/gitlab/github_gists_import/importer/gist_importer.rb
+++ b/lib/gitlab/github_gists_import/importer/gist_importer.rb
@@ -4,10 +4,13 @@ module Gitlab
module GithubGistsImport
module Importer
class GistImporter
- attr_reader :gist, :user
+ attr_reader :gist, :user, :snippet
FileCountLimitError = Class.new(StandardError)
+ RepoSizeLimitError = Class.new(StandardError)
+ SnippetRepositoryError = Class.new(StandardError)
FILE_COUNT_LIMIT_MESSAGE = 'Snippet maximum file count exceeded'
+ REPO_SIZE_LIMIT_MESSAGE = 'Snippet repository size exceeded'
# gist - An instance of `Gitlab::GithubGistsImport::Representation::Gist`.
def initialize(gist, user_id)
@@ -16,12 +19,15 @@ module Gitlab
end
def execute
- snippet = build_snippet
- import_repository(snippet) if snippet.save!
+ validate_gist!
- return ServiceResponse.success unless max_snippet_files_count_exceeded?(snippet)
+ @snippet = build_snippet
+ import_repository if snippet.save!
+ validate_repository!
- fail_and_track(snippet)
+ ServiceResponse.success
+ rescue FileCountLimitError, RepoSizeLimitError, SnippetRepositoryError => exception
+ fail_and_track(snippet, exception)
end
private
@@ -40,13 +46,13 @@ module Gitlab
PersonalSnippet.new(attrs)
end
- def import_repository(snippet)
+ def import_repository
resolved_address = get_resolved_address
snippet.create_repository
snippet.repository.fetch_as_mirror(gist.git_pull_url, forced: true, resolved_address: resolved_address)
rescue StandardError
- remove_snippet_and_repository(snippet)
+ remove_snippet_and_repository
raise
end
@@ -61,11 +67,19 @@ module Gitlab
host.present? ? validated_pull_url.host.to_s : ''
end
- def max_snippet_files_count_exceeded?(snippet)
- snippet.all_files.size > Snippet.max_file_limit
+ def check_gist_files_count!
+ return if gist.files.count <= Snippet.max_file_limit
+
+ raise FileCountLimitError, FILE_COUNT_LIMIT_MESSAGE
end
- def remove_snippet_and_repository(snippet)
+ def check_gist_repo_size!
+ return if gist.total_files_size <= Gitlab::CurrentSettings.snippet_size_limit
+
+ raise RepoSizeLimitError, REPO_SIZE_LIMIT_MESSAGE
+ end
+
+ def remove_snippet_and_repository
snippet.repository.remove if snippet.repository_exists?
snippet.destroy
end
@@ -74,10 +88,21 @@ module Gitlab
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
- def fail_and_track(snippet)
- remove_snippet_and_repository(snippet)
+ def fail_and_track(snippet, exception)
+ remove_snippet_and_repository if snippet
+
+ ServiceResponse.error(message: exception.message).track_exception(as: exception.class)
+ end
+
+ def validate_gist!
+ check_gist_files_count!
+ check_gist_repo_size!
+ end
+
+ def validate_repository!
+ result = Snippets::RepositoryValidationService.new(user, snippet).execute
- ServiceResponse.error(message: FILE_COUNT_LIMIT_MESSAGE).track_exception(as: FileCountLimitError)
+ raise SnippetRepositoryError, result.message if result.error?
end
end
end
diff --git a/lib/gitlab/github_gists_import/representation/gist.rb b/lib/gitlab/github_gists_import/representation/gist.rb
index 0d309a98f38..674da4f3400 100644
--- a/lib/gitlab/github_gists_import/representation/gist.rb
+++ b/lib/gitlab/github_gists_import/representation/gist.rb
@@ -65,6 +65,10 @@ module Gitlab
def github_identifiers
{ id: id }
end
+
+ def total_files_size
+ files.values.sum { |f| f[:size].to_i }
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a0eec5283e0..673b6e60a01 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -28930,9 +28930,15 @@ msgstr ""
msgid "MlExperimentTracking|Experiment removed"
msgstr ""
+msgid "MlExperimentTracking|Experiments keep track of comparable model candidates, and determine which parameters provides the best performance. Create experiments using the MLflow client"
+msgstr ""
+
msgid "MlExperimentTracking|Filter candidates"
msgstr ""
+msgid "MlExperimentTracking|Get started with model experiments!"
+msgstr ""
+
msgid "MlExperimentTracking|ID"
msgstr ""
@@ -28972,9 +28978,6 @@ msgstr ""
msgid "MlExperimentTracking|No candidates logged for the query. Create new candidates using the MLflow client."
msgstr ""
-msgid "MlExperimentTracking|No experiments"
-msgstr ""
-
msgid "MlExperimentTracking|No name"
msgstr ""
@@ -28984,9 +28987,6 @@ msgstr ""
msgid "MlExperimentTracking|Status"
msgstr ""
-msgid "MlExperimentTracking|There are no logged experiments for this project. Create a new experiment using the MLflow client."
-msgstr ""
-
msgid "Modal updated"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index e8d3a435766..bf9def83a01 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 10', '>= 10.3.0', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 10', '>= 10.4.0', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 0.4.0', require: false
gem 'activesupport', '~> 6.1.7.2' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.20.0'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 9b2b8acfb87..bbc06686b10 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -102,7 +102,7 @@ GEM
gitlab (4.18.0)
httparty (~> 0.18)
terminal-table (>= 1.5.1)
- gitlab-qa (10.3.0)
+ gitlab-qa (10.4.0)
activesupport (~> 6.1)
gitlab (~> 4.18.0)
http (~> 5.0)
@@ -327,7 +327,7 @@ DEPENDENCIES
faraday-retry (~> 2.1)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 10, >= 10.3.0)
+ gitlab-qa (~> 10, >= 10.4.0)
gitlab_quality-test_tooling (~> 0.4.0)
influxdb-client (~> 2.9)
knapsack (~> 4.0)
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index 18e7d7d1c1c..36b078adb48 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -76,7 +76,7 @@ then
echo
echo ' ✖ ERROR: The number of README.md file(s) has changed. Use index.md instead of README.md.' >&2
echo ' ✖ If removing a README.md file, update NUMBER_READMES in lint-doc.sh.' >&2
- echo ' https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#work-with-directories-and-files'
+ echo ' https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files' >&2
echo
((ERRORCODE++))
fi
@@ -92,7 +92,34 @@ then
echo
echo ' ✖ ERROR: The number of directory names containing dashes has changed. Use underscores instead of dashes for the directory names.' >&2
echo ' ✖ If removing a directory containing dashes, update NUMBER_DASHES in lint-doc.sh.' >&2
- echo ' https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#work-with-directories-and-files'
+ echo ' https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files' >&2
+ echo
+ ((ERRORCODE++))
+fi
+
+# Do not use uppercase letters in directory and file names, use all lowercase instead.
+# (find always returns 0, so we use the grep hack https://serverfault.com/a/225827)
+FIND_UPPERCASE_DIRS=$(find doc -type d -name "*[[:upper:]]*")
+echo '=> Checking for directory names containing an uppercase letter...'
+echo
+if echo "${FIND_UPPERCASE_DIRS}" | grep . &>/dev/null
+then
+ echo '✖ ERROR: Found one or more directories with an uppercase letter in their name. Use lowercase instead of uppercase for the directory names.' >&2
+ echo 'https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files' >&2
+ echo
+ echo "${FIND_UPPERCASE_DIRS}"
+ echo
+ ((ERRORCODE++))
+fi
+FIND_UPPERCASE_FILES=$(find doc -type f -name "*[[:upper:]]*.md")
+echo '=> Checking for file names containing an uppercase letter...'
+echo
+if echo "${FIND_UPPERCASE_FILES}" | grep . &>/dev/null
+then
+ echo '✖ ERROR: Found one or more file names with an uppercase letter in their name. Use lowercase instead of uppercase for the file names.' >&2
+ echo 'https://docs.gitlab.com/ee/development/documentation/site_architecture/folder_structure.html#work-with-directories-and-files' >&2
+ echo
+ echo "${FIND_UPPERCASE_FILES}"
echo
((ERRORCODE++))
fi
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index d3ccde3d2e1..db72413ebe0 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -605,17 +605,17 @@ RSpec.describe 'Pipelines', :js, feature_category: :projects do
wait_for_requests
end
- it 'changes the Pipeline ID column for Pipeline IID' do
- page.find('[data-testid="pipeline-key-collapsible-box"]').click
+ it 'changes the Pipeline ID column link to Pipeline IID and persists', :aggregate_failures do
+ expect(page).to have_link(text: "##{pipeline.id}")
- within '.gl-new-dropdown-contents' do
- dropdown_options = page.find_all '.gl-new-dropdown-item'
+ select_from_listbox('Show Pipeline IID', from: 'Show Pipeline ID')
- dropdown_options[1].click
- end
+ expect(page).to have_link(text: "##{pipeline.iid}")
+
+ visit project_pipelines_path(project)
+ wait_for_requests
- expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline'
- expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
+ expect(page).to have_link(text: "##{pipeline.iid}")
end
end
end
diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js
index bd006a6b3ce..b9a8bc171db 100644
--- a/spec/frontend/issues/list/mock_data.js
+++ b/spec/frontend/issues/list/mock_data.js
@@ -154,6 +154,22 @@ export const setSortPreferenceMutationResponseWithErrors = {
},
};
+export const setIdTypePreferenceMutationResponse = {
+ data: {
+ userPreferencesUpdate: {
+ errors: [],
+ },
+ },
+};
+
+export const setIdTypePreferenceMutationResponseWithErrors = {
+ data: {
+ userPreferencesUpdate: {
+ errors: ['oh no!'],
+ },
+ },
+};
+
export const locationSearch = [
'?search=find+issues',
'author_username=homer',
diff --git a/spec/frontend/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index_spec.js b/spec/frontend/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index_spec.js
index 0c83be1822e..c1158fd2ca4 100644
--- a/spec/frontend/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index_spec.js
+++ b/spec/frontend/ml/experiment_tracking/routes/experiments/index/components/ml_experiments_index_spec.js
@@ -46,8 +46,8 @@ describe('MlExperimentsIndex', () => {
expect(findPagination().exists()).toBe(false);
});
- it('does not render header', () => {
- expect(findTitleHeader().exists()).toBe(false);
+ it('renders header', () => {
+ expect(findTitleHeader().exists()).toBe(true);
});
});
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index f0772bce167..f6021041b38 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -1,5 +1,13 @@
import '~/commons';
-import { GlButton, GlEmptyState, GlFilteredSearch, GlLoadingIcon, GlPagination } from '@gitlab/ui';
+import {
+ GlButton,
+ GlEmptyState,
+ GlFilteredSearch,
+ GlLoadingIcon,
+ GlPagination,
+ GlCollapsibleListbox,
+} from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { chunk } from 'lodash';
@@ -10,8 +18,10 @@ import { TEST_HOST } from 'helpers/test_constants';
import { mockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
import Api from '~/api';
import { createAlert, VARIANT_WARNING } from '~/alert';
+import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
@@ -22,9 +32,14 @@ import { RAW_TEXT_WARNING, TRACKING_CATEGORIES } from '~/pipelines/constants';
import Store from '~/pipelines/stores/pipelines_store';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import {
+ setIdTypePreferenceMutationResponse,
+ setIdTypePreferenceMutationResponseWithErrors,
+} from 'jest/issues/list/mock_data';
import { stageReply, users, mockSearch, branches } from './mock_data';
+jest.mock('@sentry/browser');
jest.mock('~/alert');
const mockProjectPath = 'twitter/flight';
@@ -38,6 +53,7 @@ const mockPipelineWithStages = mockPipelinesResponse.pipelines.find(
describe('Pipelines', () => {
let wrapper;
+ let mockApollo;
let mock;
let trackingSpy;
@@ -70,6 +86,7 @@ describe('Pipelines', () => {
const findNavigationControls = () => wrapper.findComponent(NavigationControls);
const findPipelinesTable = () => wrapper.findComponent(PipelinesTableComponent);
const findTablePagination = () => wrapper.findComponent(TablePagination);
+ const findPipelineKeyCollapsibleBoxVue = () => wrapper.findComponent(GlCollapsibleListbox);
const findTab = (tab) => wrapper.findByTestId(`pipelines-tab-${tab}`);
const findPipelineKeyCollapsibleBox = () => wrapper.findByTestId('pipeline-key-collapsible-box');
@@ -81,6 +98,9 @@ describe('Pipelines', () => {
const findPipelineUrlLinks = () => wrapper.findAll('[data-testid="pipeline-url-link"]');
const createComponent = (props = defaultProps) => {
+ const { mutationMock, ...restProps } = props;
+ mockApollo = createMockApollo([[setSortPreferenceMutation, mutationMock]]);
+
wrapper = extendedWrapper(
mount(PipelinesComponent, {
provide: {
@@ -95,8 +115,9 @@ describe('Pipelines', () => {
defaultBranchName: mockDefaultBranchName,
endpoint: mockPipelinesEndpoint,
params: {},
- ...props,
+ ...restProps,
},
+ apolloProvider: mockApollo,
}),
);
};
@@ -115,6 +136,7 @@ describe('Pipelines', () => {
afterEach(() => {
mock.reset();
+ mockApollo = null;
window.history.pushState.mockReset();
});
@@ -349,6 +371,45 @@ describe('Pipelines', () => {
});
});
+ describe('when user changes Show Pipeline ID to Show Pipeline IID', () => {
+ const mockFilteredPipeline = mockPipelinesResponse.pipelines[0];
+
+ beforeEach(() => {
+ gon.current_user_id = 1;
+ });
+
+ it('should change the text to Show Pipeline IID', async () => {
+ expect(findPipelineKeyCollapsibleBox().exists()).toBe(true);
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFilteredPipeline.id}`);
+ findPipelineKeyCollapsibleBoxVue().vm.$emit('select', 'iid');
+
+ await waitForPromises();
+
+ expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFilteredPipeline.iid}`);
+ });
+
+ it('calls mutation to save idType preference', () => {
+ const mutationMock = jest.fn().mockResolvedValue(setIdTypePreferenceMutationResponse);
+ createComponent({ ...defaultProps, mutationMock });
+
+ findPipelineKeyCollapsibleBoxVue().vm.$emit('select', 'iid');
+
+ expect(mutationMock).toHaveBeenCalledWith({ input: { visibilityPipelineIdType: 'IID' } });
+ });
+
+ it('captures error when mutation response has errors', async () => {
+ const mutationMock = jest
+ .fn()
+ .mockResolvedValue(setIdTypePreferenceMutationResponseWithErrors);
+ createComponent({ ...defaultProps, mutationMock });
+
+ findPipelineKeyCollapsibleBoxVue().vm.$emit('select', 'iid');
+ await waitForPromises();
+
+ expect(Sentry.captureException).toHaveBeenCalledWith(new Error('oh no!'));
+ });
+ });
+
describe('when user triggers a filtered search with raw text', () => {
beforeEach(async () => {
findFilteredSearch().vm.$emit('submit', ['rawText']);
diff --git a/spec/helpers/ci/pipelines_helper_spec.rb b/spec/helpers/ci/pipelines_helper_spec.rb
index 6463da7c53f..61583ca1173 100644
--- a/spec/helpers/ci/pipelines_helper_spec.rb
+++ b/spec/helpers/ci/pipelines_helper_spec.rb
@@ -121,7 +121,8 @@ RSpec.describe Ci::PipelinesHelper do
:has_gitlab_ci,
:pipeline_editor_path,
:suggested_ci_templates,
- :full_path])
+ :full_path,
+ :visibility_pipeline_id_type])
end
describe 'when the project is eligible for the `ios_specific_templates` experiment' do
@@ -193,4 +194,27 @@ RSpec.describe Ci::PipelinesHelper do
end
end
end
+
+ describe '#visibility_pipeline_id_type' do
+ subject { helper.visibility_pipeline_id_type }
+
+ context 'when user is not signed in' do
+ it 'shows default pipeline id type' do
+ expect(subject).to eq('id')
+ end
+ end
+
+ context 'when user is signed in' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ user.user_preference.update!(visibility_pipeline_id_type: 'iid')
+ end
+
+ it 'shows user preference pipeline id type' do
+ expect(subject).to eq('iid')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb b/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb
index 6bfbfbdeddf..cbcd9b83c15 100644
--- a/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb
+++ b/spec/lib/gitlab/github_gists_import/importer/gist_importer_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_category: :importers do
- subject { described_class.new(gist_object, user.id).execute }
+ subject { described_class.new(gist_object, user.id) }
let_it_be(:user) { create(:user) }
let(:created_at) { Time.utc(2022, 1, 9, 12, 15) }
@@ -18,7 +18,8 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
first_file: gist_file,
git_pull_url: url,
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ total_files_size: Gitlab::CurrentSettings.snippet_size_limit
)
end
@@ -36,34 +37,103 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
describe '#execute' do
context 'when success' do
+ let(:validator_result) do
+ instance_double(ServiceResponse, error?: false)
+ end
+
it 'creates expected snippet and snippet repository' do
+ expect_next_instance_of(Snippets::RepositoryValidationService) do |validator|
+ expect(validator).to receive(:execute).and_return(validator_result)
+ end
+
expect_next_instance_of(Repository) do |repository|
expect(repository).to receive(:fetch_as_mirror)
end
- expect { subject }.to change { user.snippets.count }.by(1)
+ expect { subject.execute }.to change { user.snippets.count }.by(1)
expect(user.snippets[0].attributes).to include expected_snippet_attrs
end
end
- context 'when file size limit exeeded' do
- before do
- files = [].tap { |array| 11.times { |n| array << ["file#{n}.txt", {}] } }.to_h
+ describe 'pre-import validations' do
+ context 'when file count limit exeeded' do
+ before do
+ files = [].tap { |array| 11.times { |n| array << ["file#{n}.txt", {}] } }.to_h
+
+ allow(gist_object).to receive(:files).and_return(files)
+ end
+
+ it 'validates input and returns error' do
+ expect(PersonalSnippet).not_to receive(:new)
+
+ result = subject.execute
+
+ expect(user.snippets.count).to eq(0)
+ expect(result.error?).to eq(true)
+ expect(result.errors).to match_array(['Snippet maximum file count exceeded'])
+ end
+ end
+
+ context 'when repo too big' do
+ before do
+ files = [{ "file1.txt" => {} }, { "file2.txt" => {} }]
+
+ allow(gist_object).to receive(:files).and_return(files)
+ allow(gist_object).to receive(:total_files_size).and_return(Gitlab::CurrentSettings.snippet_size_limit + 1)
+ end
+
+ it 'validates input and returns error' do
+ expect(PersonalSnippet).not_to receive(:new)
+
+ result = subject.execute
+
+ expect(result.error?).to eq(true)
+ expect(result.errors).to match_array(['Snippet repository size exceeded'])
+ end
+ end
+ end
+ describe 'post-import validations' do
+ let(:files) { { "file1.txt" => {}, "file2.txt" => {} } }
+
+ before do
allow(gist_object).to receive(:files).and_return(files)
allow_next_instance_of(Repository) do |repository|
allow(repository).to receive(:fetch_as_mirror)
- allow(repository).to receive(:empty?).and_return(false)
- allow(repository).to receive(:ls_files).and_return(files.keys)
+ end
+ allow_next_instance_of(Snippets::RepositoryValidationService) do |validator|
+ allow(validator).to receive(:execute).and_return(validator_result)
end
end
- it 'returns error' do
- result = subject
+ context 'when file count limit exeeded' do
+ let(:validator_result) do
+ instance_double(ServiceResponse, error?: true, message: 'Error: Repository files count over the limit')
+ end
- expect(user.snippets.count).to eq(0)
- expect(result.error?).to eq(true)
- expect(result.errors).to match_array(['Snippet maximum file count exceeded'])
+ it 'returns error' do
+ expect(subject).to receive(:remove_snippet_and_repository).and_call_original
+
+ result = subject.execute
+
+ expect(result).to be_error
+ expect(result.errors).to match_array(['Error: Repository files count over the limit'])
+ end
+ end
+
+ context 'when repo too big' do
+ let(:validator_result) do
+ instance_double(ServiceResponse, error?: true, message: 'Error: Repository size is above the limit.')
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:remove_snippet_and_repository).and_call_original
+
+ result = subject.execute
+
+ expect(result).to be_error
+ expect(result.errors).to match_array(['Error: Repository size is above the limit.'])
+ end
end
end
@@ -71,7 +141,8 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
let(:gist_file) { { file_name: '_Summary.md', file_content: nil } }
it 'raises an error' do
- expect { subject }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Content can't be blank")
+ expect { subject.execute }
+ .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Content can't be blank")
end
end
@@ -82,7 +153,9 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
expect(repository).to receive(:remove)
end
- expect { subject }.to raise_error(Gitlab::Shell::Error)
+ expect(subject).to receive(:remove_snippet_and_repository).and_call_original
+
+ expect { subject.execute }.to raise_error(Gitlab::Shell::Error)
expect(user.snippets.count).to eq(0)
end
end
@@ -103,7 +176,7 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
allow_localhost: true, allow_local_network: true)
.and_raise(Gitlab::UrlBlocker::BlockedUrlError)
- expect { subject }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { subject.execute }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
end
end
@@ -120,7 +193,7 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
allow_localhost: false, allow_local_network: false)
.and_raise(Gitlab::UrlBlocker::BlockedUrlError)
- expect { subject }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ expect { subject.execute }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
end
end
end