diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-16 21:07:25 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-16 21:07:25 +0000 |
commit | 32cfd14a94117d1e56524727e7d1b649493f5790 (patch) | |
tree | 9de87db328f7a1e83e0e195a5e16aad78cd2b24e | |
parent | b6b701cf9d08253d7c6f1e7500a09b1e373e18e3 (diff) | |
download | gitlab-ce-32cfd14a94117d1e56524727e7d1b649493f5790.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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: - + 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 Binary files differdeleted file mode 100644 index 50313f71f4a..00000000000 --- a/doc/user/profile/img/saved_replies_dropdown_v15_10.png +++ /dev/null diff --git a/doc/user/profile/img/saved_replies_dropdown_v16_0.png b/doc/user/profile/img/saved_replies_dropdown_v16_0.png Binary files differnew file mode 100644 index 00000000000..4608484a496 --- /dev/null +++ b/doc/user/profile/img/saved_replies_dropdown_v16_0.png 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 |