diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-07 09:08:11 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-07 09:08:11 +0000 |
commit | bc9a474793f7b9dca48b8aa29661039d75673d0c (patch) | |
tree | 40f8db9ca9641a8b46f69580fe65dc44c6f430a0 | |
parent | 44c74f7b06002162c0d6bcc7c8f94f6b1a56d438 (diff) | |
download | gitlab-ce-bc9a474793f7b9dca48b8aa29661039d75673d0c.tar.gz |
Add latest changes from gitlab-org/gitlab@master
31 files changed, 651 insertions, 89 deletions
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue new file mode 100644 index 00000000000..efc60c9c037 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue @@ -0,0 +1,57 @@ +<script> +import { GlKeysetPagination } from '@gitlab/ui'; +import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue'; +import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; + +export default { + components: { + VersionRow, + GlKeysetPagination, + PackagesListLoader, + }, + props: { + versions: { + type: Array, + required: true, + default: () => [], + }, + pageInfo: { + type: Object, + required: true, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + }, + computed: { + showPagination() { + return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage; + }, + isListEmpty() { + return this.versions.length === 0; + }, + }, +}; +</script> +<template> + <div> + <div v-if="isLoading"> + <packages-list-loader /> + </div> + <slot v-else-if="isListEmpty" name="empty-state"></slot> + <div v-else> + <version-row v-for="version in versions" :key="version.id" :package-entity="version" /> + <div class="gl-display-flex gl-justify-content-center"> + <gl-keyset-pagination + v-if="showPagination" + v-bind="pageInfo" + class="gl-mt-3" + @prev="$emit('prev-page')" + @next="$emit('next-page')" + /> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql index 8e50c95b10b..51e0ab5aba8 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql @@ -1,4 +1,10 @@ -query getPackageDetails($id: PackagesPackageID!) { +query getPackageDetails( + $id: PackagesPackageID! + $first: Int + $last: Int + $after: String + $before: String +) { package(id: $id) { id name @@ -55,7 +61,7 @@ query getPackageDetails($id: PackagesPackageID!) { downloadPath } } - versions(first: 100) { + versions(after: $after, before: $before, first: $first, last: $last) { nodes { id name @@ -69,6 +75,12 @@ query getPackageDetails($id: PackagesPackageID!) { } } } + pageInfo { + hasNextPage + hasPreviousPage + endCursor + startCursor + } } dependencyLinks { nodes { diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue index eeed56b77c3..c59dcaee411 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue @@ -22,7 +22,7 @@ import InstallationCommands from '~/packages_and_registries/package_registry/com import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue'; -import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue'; +import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; import { PACKAGE_TYPE_NUGET, @@ -48,6 +48,7 @@ import { DELETE_MODAL_CONTENT, DELETE_ALL_PACKAGE_FILES_MODAL_CONTENT, DELETE_LAST_PACKAGE_FILE_MODAL_CONTENT, + GRAPHQL_PAGE_SIZE, } from '~/packages_and_registries/package_registry/constants'; import destroyPackageFilesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_files.mutation.graphql'; @@ -65,13 +66,13 @@ export default { GlTabs, GlSprintf, PackageTitle, - VersionRow, DependencyRow, PackageHistory, AdditionalMetadata, InstallationCommands, PackageFiles, DeletePackage, + PackageVersionsList, }, directives: { GlTooltip: GlTooltipDirective, @@ -132,6 +133,7 @@ export default { queryVariables() { return { id: convertToGraphQLId('Packages::Package', this.packageId), + first: GRAPHQL_PAGE_SIZE, }; }, packageFiles() { @@ -157,6 +159,9 @@ export default { hasVersions() { return this.packageEntity.versions?.nodes?.length > 0; }, + versionPageInfo() { + return this.packageEntity?.versions?.pageInfo ?? {}; + }, packageDependencies() { return this.packageEntity.dependencyLinks?.nodes || []; }, @@ -264,6 +269,34 @@ export default { resetDeleteModalContent() { this.deletePackageModalContent = DELETE_MODAL_CONTENT; }, + updateQuery(_, { fetchMoreResult }) { + return fetchMoreResult; + }, + fetchPreviousVersionsPage() { + const variables = { + ...this.queryVariables, + first: null, + last: GRAPHQL_PAGE_SIZE, + before: this.versionPageInfo?.startCursor, + }; + this.$apollo.queries.packageEntity.fetchMore({ + variables, + updateQuery: this.updateQuery, + }); + }, + fetchNextVersionsPage() { + const variables = { + ...this.queryVariables, + first: GRAPHQL_PAGE_SIZE, + last: null, + after: this.versionPageInfo?.endCursor, + }; + + this.$apollo.queries.packageEntity.fetchMore({ + variables, + updateQuery: this.updateQuery, + }); + }, }, i18n: { DELETE_MODAL_TITLE, @@ -271,6 +304,7 @@ export default { deleteFileModalContent: s__( `PackageRegistry|You are about to delete %{filename}. This is a destructive action that may render your package unusable. Are you sure?`, ), + otherVersionsTabTitle: __('Other versions'), }, modal: { packageDeletePrimaryAction: { @@ -303,7 +337,7 @@ export default { :description="s__('PackageRegistry|There was a problem fetching the details for this package.')" :svg-path="emptyListIllustration" /> - <div v-else-if="!isLoading" class="packages-app"> + <div v-else-if="projectName" class="packages-app"> <package-title :package-entity="packageEntity"> <template #delete-button> <gl-button @@ -358,14 +392,20 @@ export default { </p> </gl-tab> - <gl-tab :title="__('Other versions')" title-item-class="js-versions-tab"> - <template v-if="hasVersions"> - <version-row v-for="v in packageEntity.versions.nodes" :key="v.id" :package-entity="v" /> - </template> - - <p v-else class="gl-mt-3" data-testid="no-versions-message"> - {{ s__('PackageRegistry|There are no other versions of this package.') }} - </p> + <gl-tab :title="$options.i18n.otherVersionsTabTitle" title-item-class="js-versions-tab" lazy> + <package-versions-list + :is-loading="isLoading" + :page-info="versionPageInfo" + :versions="packageEntity.versions.nodes" + @prev-page="fetchPreviousVersionsPage" + @next-page="fetchNextVersionsPage" + > + <template #empty-state> + <p class="gl-mt-3" data-testid="no-versions-message"> + {{ s__('PackageRegistry|There are no other versions of this package.') }} + </p> + </template> + </package-versions-list> </gl-tab> </gl-tabs> diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index a8d3e980703..e05adc5cd0e 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -24,7 +24,7 @@ module DiffHelper end def show_only_context_commits? - !!params[:only_context_commits] || @merge_request&.commits&.empty? + !!params[:only_context_commits] || @merge_request.has_no_commits? end def diff_options diff --git a/config/open_api.yml b/config/open_api.yml index f043932c71a..5502577e201 100644 --- a/config/open_api.yml +++ b/config/open_api.yml @@ -43,6 +43,8 @@ metadata: description: Operations related to merge requests - name: metadata description: Operations related to metadata of the GitLab instance + - name: project_export + description: Operations related to export projects - name: project_hooks description: Operations related to project hooks - name: project_import_bitbucket @@ -55,5 +57,7 @@ metadata: description: Operations related to releases - name: suggestions description: Operations related to suggestions + - name: system_hooks + description: Operations related to system hooks - name: unleash_api - description: Operations related to Unleash API + description: Operations related to Unleash API
\ No newline at end of file diff --git a/db/post_migrate/20221104074652_add_temp_index_for_project_statistics_upload_size_migration.rb b/db/post_migrate/20221104074652_add_temp_index_for_project_statistics_upload_size_migration.rb new file mode 100644 index 00000000000..b6ee636fa9b --- /dev/null +++ b/db/post_migrate/20221104074652_add_temp_index_for_project_statistics_upload_size_migration.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddTempIndexForProjectStatisticsUploadSizeMigration < Gitlab::Database::Migration[2.0] + INDEX_PROJECT_STATSISTICS_UPLOADS_SIZE = 'tmp_index_project_statistics_uploads_size' + + disable_ddl_transaction! + + def up + # Temporary index is to be used to trigger refresh for all project_statistics with + # upload_size <> 0 + add_concurrent_index :project_statistics, [:project_id], + name: INDEX_PROJECT_STATSISTICS_UPLOADS_SIZE, + where: "uploads_size <> 0" + end + + def down + remove_concurrent_index_by_name :project_statistics, INDEX_PROJECT_STATSISTICS_UPLOADS_SIZE + end +end diff --git a/db/schema_migrations/20221104074652 b/db/schema_migrations/20221104074652 new file mode 100644 index 00000000000..460f21a3f6e --- /dev/null +++ b/db/schema_migrations/20221104074652 @@ -0,0 +1 @@ +167032d562467c3d6be9e6c6c8c072f117e23798db35301f95386130ae115a00
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 2c69d2baea8..6cfd5aafc22 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -31217,6 +31217,8 @@ CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING CREATE INDEX tmp_index_project_statistics_cont_registry_size ON project_statistics USING btree (project_id) WHERE (container_registry_size = 0); +CREATE INDEX tmp_index_project_statistics_uploads_size ON project_statistics USING btree (project_id) WHERE (uploads_size <> 0); + CREATE INDEX tmp_index_vulnerability_occurrences_on_id_and_scanner_id ON vulnerability_occurrences USING btree (id, scanner_id) WHERE (report_type = ANY (ARRAY[7, 99])); CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name); diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 7a7fc7b0403..6ef1666147c 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -6980,6 +6980,29 @@ The edge type for [`ContainerRepositoryTag`](#containerrepositorytag). | <a id="containerrepositorytagedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="containerrepositorytagedgenode"></a>`node` | [`ContainerRepositoryTag`](#containerrepositorytag) | The item at the end of the edge. | +#### `ContributionAnalyticsContributionConnection` + +The connection type for [`ContributionAnalyticsContribution`](#contributionanalyticscontribution). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="contributionanalyticscontributionconnectionedges"></a>`edges` | [`[ContributionAnalyticsContributionEdge]`](#contributionanalyticscontributionedge) | A list of edges. | +| <a id="contributionanalyticscontributionconnectionnodes"></a>`nodes` | [`[ContributionAnalyticsContribution]`](#contributionanalyticscontribution) | A list of nodes. | +| <a id="contributionanalyticscontributionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `ContributionAnalyticsContributionEdge` + +The edge type for [`ContributionAnalyticsContribution`](#contributionanalyticscontribution). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="contributionanalyticscontributionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | +| <a id="contributionanalyticscontributionedgenode"></a>`node` | [`ContributionAnalyticsContribution`](#contributionanalyticscontribution) | The item at the end of the edge. | + #### `CoverageFuzzingCorpusConnection` The connection type for [`CoverageFuzzingCorpus`](#coveragefuzzingcorpus). @@ -11236,6 +11259,24 @@ A tag from a container repository. | <a id="containerrepositorytagshortrevision"></a>`shortRevision` | [`String`](#string) | Short revision of the tag. | | <a id="containerrepositorytagtotalsize"></a>`totalSize` | [`BigInt`](#bigint) | Size of the tag. | +### `ContributionAnalyticsContribution` + +Represents the contributions of a user. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="contributionanalyticscontributionissuesclosed"></a>`issuesClosed` | [`Int`](#int) | Number of issues closed by the user. | +| <a id="contributionanalyticscontributionissuescreated"></a>`issuesCreated` | [`Int`](#int) | Number of issues created by the user. | +| <a id="contributionanalyticscontributionmergerequestsapproved"></a>`mergeRequestsApproved` | [`Int`](#int) | Number of merge requests approved by the user. | +| <a id="contributionanalyticscontributionmergerequestsclosed"></a>`mergeRequestsClosed` | [`Int`](#int) | Number of merge requests closed by the user. | +| <a id="contributionanalyticscontributionmergerequestscreated"></a>`mergeRequestsCreated` | [`Int`](#int) | Number of merge requests created by the user. | +| <a id="contributionanalyticscontributionmergerequestsmerged"></a>`mergeRequestsMerged` | [`Int`](#int) | Number of merge requests merged by the user. | +| <a id="contributionanalyticscontributionrepopushed"></a>`repoPushed` | [`Int`](#int) | Number of repository pushes the user made. | +| <a id="contributionanalyticscontributiontotalevents"></a>`totalEvents` | [`Int`](#int) | Total number of events contributed by the user. | +| <a id="contributionanalyticscontributionuser"></a>`user` | [`UserCore`](#usercore) | Contributor User object. | + ### `CoverageFuzzingCorpus` Corpus for a coverage fuzzing job. @@ -13079,6 +13120,23 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="groupcontainerrepositoriesname"></a>`name` | [`String`](#string) | Filter the container repositories by their name. | | <a id="groupcontainerrepositoriessort"></a>`sort` | [`ContainerRepositorySort`](#containerrepositorysort) | Sort container repositories by this criteria. | +##### `Group.contributions` + +Provides the aggregated contributions by users within the group and its subgroups. + +Returns [`ContributionAnalyticsContributionConnection`](#contributionanalyticscontributionconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="groupcontributionsfrom"></a>`from` | [`ISO8601Date!`](#iso8601date) | Start date of the reporting time range. | +| <a id="groupcontributionsto"></a>`to` | [`ISO8601Date!`](#iso8601date) | End date of the reporting time range. The end date must be within 31 days after the start date. | + ##### `Group.descendantGroups` List of descendant groups of this group. diff --git a/doc/architecture/blueprints/work_items/index.md b/doc/architecture/blueprints/work_items/index.md index ee9688eeb39..89934801633 100644 --- a/doc/architecture/blueprints/work_items/index.md +++ b/doc/architecture/blueprints/work_items/index.md @@ -58,14 +58,19 @@ All Work Item types share the same pool of predefined widgets and are customized ### Work Item widget types (updating) -- assignees -- description -- hierarchy -- iteration -- labels -- start and due date -- verification status -- weight +| widget type | feature flag | +|---|---|---| +| assignees | | +| description | | +| hierarchy | | +| [iteration](https://gitlab.com/gitlab-org/gitlab/-/issues/367456) | work_items_mvc_2 | +| [milestone](https://gitlab.com/gitlab-org/gitlab/-/issues/367463) | work_items_mvc_2 | +| labels | | +| start and due date | | +| status\* | | +| weight | | + +\* status is not currently a widget, but a part of the root work item, similar to title ### Work Item view @@ -75,6 +80,16 @@ The new frontend view that renders Work Items of any type using global Work Item Task is a special Work Item type. Tasks can be added to issues as child items and can be displayed in the modal on the issue view. +### Feature flags + +Since this is a large project with numerous moving parts, feature flags are being used to track promotions of available widgets. The table below shows the different feature flags that are being used, and the audience that they are available to. + +| feature flag name | audience | +|---|---| +| `work_items` | defaulted to on | +| `work_items_mvc` | `gitlab-org`, `gitlab-com` | +| `work_items_mvc_2` | `gitlab-org/plan-stage` | + ## Motivation Work Items main goal is to enhance the planning toolset to become the most popular collaboration tool for knowledge workers in any industry. diff --git a/doc/ci/pipelines/downstream_pipelines.md b/doc/ci/pipelines/downstream_pipelines.md index a711e938503..522466487a2 100644 --- a/doc/ci/pipelines/downstream_pipelines.md +++ b/doc/ci/pipelines/downstream_pipelines.md @@ -122,7 +122,7 @@ the value of the [`$CI_PIPELINE_SOURCE` predefined variable](../variables/predef for all jobs is: - `pipeline` for multi-project pipelines. -- `parent` for parent-child pipelines. +- `parent_pipeline` for parent-child pipelines. For example, to control jobs in multi-project pipelines in a project that also runs merge request pipelines: diff --git a/doc/ci/services/index.md b/doc/ci/services/index.md index 18932512a75..830b9406c6e 100644 --- a/doc/ci/services/index.md +++ b/doc/ci/services/index.md @@ -392,6 +392,46 @@ time. 1. Check the exit status of build script. 1. Remove the build container and all created service containers. +## Capturing service container logs + +Logs generated by applications running in service containers can be captured for subsequent examination and debugging. +You might want to look at service container's logs when the service container has started successfully, but is not +behaving es expected, leading to job failures. The logs can indicate missing or incorrect configuration of the service +within the container. + +`CI_DEBUG_SERVICES` should only by enabled when service containers are being actively debugged as there are both storage +and performance consequences to capturing service container logs. + +To enable service logging, add the `CI_DEBUG_SERVICES` variable to the project's +`.gitlab-ci.yml` file: + +```yaml +variables: + CI_DEBUG_SERVICES: "true" +``` + +Accepted values are: + +- Enabled: `TRUE`, `true`, `True` +- Disabled: `FALSE`, `false`, `False` + +Any other values will result in an error message and effectively disable the feature. + +When enabled, logs for _all_ service containers will be captured and streamed into the jobs trace log concurrently with +other logs. Logs from each container will be prefixed with the container's aliases, and displayed in a different color. + +NOTE: +You may want to adjust the logging level in the service container for which you want to capture logs since the default +logging level may not provide sufficient details to diagnose job failures. + +WARNING: +Enabling `CI_DEBUG_SERVICES` _may_ result in masked variables being revealed. When `CI_DEBUG_SERVICES` is enabled, +service container logs and the CI job's logs are streamed to the job's trace log _concurrently_, which makes it possible +for a service container log to be inserted _inside_ a job's masked log. This would thwart the variable masking mechanism +and result in the masked variable being revealed. + +See [Mask a CI/CD Variable](../variables/index.md#mask-a-cicd-variable) + ## Debug a job locally The following commands are run without root privileges. You should be diff --git a/doc/ci/variables/index.md b/doc/ci/variables/index.md index 7ad42aaf96b..cd592310621 100644 --- a/doc/ci/variables/index.md +++ b/doc/ci/variables/index.md @@ -357,15 +357,22 @@ The value of the variable must: - The `~` character (In [GitLab 13.12 and later](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61517)). - Not match the name of an existing predefined or custom CI/CD variable. -NOTE: -Masking a CI/CD variable is not a guaranteed way to prevent malicious users from accessing -variable values. To make variables more secure, you can [use external secrets](../secrets/index.md). - WARNING: -Due to a technical limitation, masked variables that are more than 4 KiB in length are not recommended. Printing such -a large value to the trace log has the potential to be [revealed](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28128). -When using GitLab Runner 14.2, only the tail of the variable, characters beyond 4KiB in length, have the potential to -be revealed. +Masking a CI/CD variable is not a guaranteed way to prevent malicious users from +accessing variable values. The masking feature is "best-effort" and there to +help when a variable is accidentally revealed. To make variables more secure, +consider using [external secrets](../secrets/index.md) and [file type variables](#cicd-variable-types) +to prevent commands such as `env`/`printenv` from printing secret variables. + +Runner versions implement masking in different ways, some with technical +limitations. Below is a table of such limitations. + +| Version from | Version to | Limitations | +| ------------ | ---------- | ------ | +| v0.1.0 | v11.8.0 | No masking functionality supported. | +| v11.9.0 | v14.1.0 | Masking of large (> 4KiB) secrets could potentially be [revealed](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28128). No sensitive URL parameter masking. | +| v14.2.0 | v15.3.0 | The tail of a large (> 4KiB) secret could potentially be [revealed](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28128). No sensitive URL parameter masking. | +| v15.7.0 | | Potential for secrets to be revealed when `CI_DEBUG_SERVICES` is enabled. For details, read about [service container logging](../services/index.md#capturing-service-container-logs). | ### Protected CI/CD variables diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index faa96d67809..852110a1415 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -46,6 +46,7 @@ as it can cause the pipeline to behave unexpectedly. | `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | The unique ID of build execution in a single executor and project. | | `CI_CONFIG_PATH` | 9.4 | 0.5 | The path to the CI/CD configuration file. Defaults to `.gitlab-ci.yml`. Read-only inside a running pipeline. | | `CI_DEBUG_TRACE` | all | 1.7 | `true` if [debug logging (tracing)](index.md#debug-logging) is enabled. | +| `CI_DEBUG_SERVICES` | 15.7 | 15.7 | `true` if [service container logging](../services/index.md#capturing-service-container-logs) is enabled. | | `CI_DEFAULT_BRANCH` | 12.4 | all | The name of the project's default branch. | | `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` | 13.7 | all | The top-level group image prefix for pulling images through the Dependency Proxy. | | `CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX` | 14.3 | all | The direct group image prefix for pulling images through the Dependency Proxy. | diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md index fd5c037fe3a..1deb4842107 100644 --- a/doc/user/profile/notifications.md +++ b/doc/user/profile/notifications.md @@ -357,13 +357,13 @@ a merge request or an issue. The following table lists all GitLab-specific email headers: | Header | Description | -|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `List-Id` | The path of the project in an RFC 2919 mailing list identifier. You can use it for email organization with filters. | | `X-GitLab-(Resource)-ID` | The ID of the resource the notification is for. The resource, for example, can be `Issue`, `MergeRequest`, `Commit`, or another such resource. | | `X-GitLab-Discussion-ID` | The ID of the thread the comment belongs to, in notification emails for comments. | | `X-GitLab-Group-Id` | The group's ID. Only present on notification emails for [epics](../group/epics/index.md). | | `X-GitLab-Group-Path` | The group's path. Only present on notification emails for [epics](../group/epics/index.md) | -| [`X-GitLab-NotificationReason`](#x-gitlab-notificationreason) | The reason for the notification. This can be `mentioned`, `assigned`, or `own_activity`. | +| `X-GitLab-NotificationReason` | The reason for the notification. [See possible values.](#x-gitlab-notificationreason). | | `X-GitLab-Pipeline-Id` | The ID of the pipeline the notification is for, in notification emails for pipelines. | | `X-GitLab-Project-Id` | The project's ID. | | `X-GitLab-Project-Path` | The project's path. | @@ -377,21 +377,35 @@ The value is one of the following, in order of priority: - `own_activity` - `assigned` +- `review_requested` - `mentioned` +- `subscribed` The reason for the notification is also included in the footer of the notification email. For example, an email with the reason `assigned` has this sentence in the footer: > You are receiving this email because you have been assigned an item on \<configured GitLab hostname>. -For example, an alert notification email can have one of -[the alert's](../../operations/incident_management/alerts.md) statuses: +#### On-call alerts notifications **(PREMIUM)** + +An [on-call alert](../../operations/incident_management/oncall_schedules.md) +notification email can have one of [the alert's](../../operations/incident_management/alerts.md) statuses: - `alert_triggered` - `alert_acknowledged` - `alert_resolved` - `alert_ignored` +#### Incident escalation notifications **(PREMIUM)** + +An [incident escalation](../../operations/incident_management/escalation_policies.md) +notification email can have one of [the incident's](../../operations/incident_management/incidents.md) status: + +- `incident_triggered` +- `incident_acknowledged` +- `incident_resolved` +- `incident_ignored` + Expanding the list of events included in the `X-GitLab-NotificationReason` header is tracked in [issue 20689](https://gitlab.com/gitlab-org/gitlab/-/issues/20689). diff --git a/lib/api/api.rb b/lib/api/api.rb index c5794db5580..7b9e56c5271 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -193,6 +193,7 @@ module API mount ::API::Metadata mount ::API::MergeRequestDiffs mount ::API::PersonalAccessTokens::SelfInformation + mount ::API::ProjectExport mount ::API::ProjectHooks mount ::API::ProjectRepositoryStorageMoves mount ::API::Releases @@ -205,6 +206,7 @@ module API mount ::API::Statistics mount ::API::Submodules mount ::API::Suggestions + mount ::API::SystemHooks mount ::API::Tags mount ::API::Unleash mount ::API::UserCounts @@ -293,7 +295,6 @@ module API mount ::API::ProjectContainerRepositories mount ::API::ProjectDebianDistributions mount ::API::ProjectEvents - mount ::API::ProjectExport mount ::API::ProjectImport mount ::API::ProjectMilestones mount ::API::ProjectPackages @@ -315,7 +316,6 @@ module API mount ::API::SidekiqMetrics mount ::API::Snippets mount ::API::Subscriptions - mount ::API::SystemHooks mount ::API::Tags mount ::API::Templates mount ::API::Terraform::Modules::V1::Packages diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 770420f8f93..2fe270bcb8a 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -244,6 +244,8 @@ module API # current_authenticated_job will be nil if user is using # a valid authentication (like PRIVATE-TOKEN) that is not CI_JOB_TOKEN not_found!('Job') unless current_authenticated_job + + ::Gitlab::ApplicationContext.push(job: current_authenticated_job) end end end diff --git a/lib/api/entities/bulk_imports/export_status.rb b/lib/api/entities/bulk_imports/export_status.rb index c9c7f34a16a..fee983c6fd8 100644 --- a/lib/api/entities/bulk_imports/export_status.rb +++ b/lib/api/entities/bulk_imports/export_status.rb @@ -4,10 +4,10 @@ module API module Entities module BulkImports class ExportStatus < Grape::Entity - expose :relation - expose :status - expose :error - expose :updated_at + expose :relation, documentation: { type: 'string', example: 'issues' } + expose :status, documentation: { type: 'string', example: 'started', values: %w[started finished failed] } + expose :error, documentation: { type: 'string', example: 'Error message' } + expose :updated_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } end end end diff --git a/lib/api/entities/hook.rb b/lib/api/entities/hook.rb index 95924321221..e24e201ac57 100644 --- a/lib/api/entities/hook.rb +++ b/lib/api/entities/hook.rb @@ -3,12 +3,18 @@ module API module Entities class Hook < Grape::Entity - expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events - expose :enable_ssl_verification + expose :id, documentation: { type: 'string', example: 1 } + expose :url, documentation: { type: 'string', example: 'https://webhook.site' } + expose :created_at, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :push_events, documentation: { type: 'boolean' } + expose :tag_push_events, documentation: { type: 'boolean' } + expose :merge_requests_events, documentation: { type: 'boolean' } + expose :repository_update_events, documentation: { type: 'boolean' } + expose :enable_ssl_verification, documentation: { type: 'boolean' } - expose :alert_status - expose :disabled_until - expose :url_variables + expose :alert_status, documentation: { type: 'symbol', example: :executable } + expose :disabled_until, documentation: { type: 'dateTime', example: '2012-05-28T04:42:42-07:00' } + expose :url_variables, documentation: { type: 'Hash', example: { "token" => "secr3t" }, is_array: true } def url_variables object.url_variables.keys.map { { key: _1 } } diff --git a/lib/api/entities/project_export_status.rb b/lib/api/entities/project_export_status.rb index ad84a45996a..9a2aeb7a6bb 100644 --- a/lib/api/entities/project_export_status.rb +++ b/lib/api/entities/project_export_status.rb @@ -5,13 +5,21 @@ module API class ProjectExportStatus < ProjectIdentity include ::API::Helpers::RelatedResourcesHelpers - expose :export_status + expose :export_status, documentation: { + type: 'string', example: 'finished', values: %w[queued started finished failed] + } expose :_links, if: lambda { |project, _options| project.export_status == :finished } do - expose :api_url do |project| + expose :api_url, documentation: { + type: 'string', + example: 'https://gitlab.example.com/api/v4/projects/1/export/download' + } do |project| expose_url(api_v4_projects_export_download_path(id: project.id)) end - expose :web_url do |project| + expose :web_url, documentation: { + type: 'string', + example: 'https://gitlab.example.com/gitlab-org/gitlab-test/download_export' + } do |project| Gitlab::Routing.url_helpers.download_export_project_url(project) end end diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 8f3f50adc86..e4e950fb603 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -16,7 +16,14 @@ module API resource :projects, requirements: { id: %r{[^/]+} } do desc 'Get export status' do detail 'This feature was introduced in GitLab 10.6.' - success Entities::ProjectExportStatus + success code: 200, model: Entities::ProjectExportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end get ':id/export' do present user_project, with: Entities::ProjectExportStatus @@ -24,6 +31,15 @@ module API desc 'Download export' do detail 'This feature was introduced in GitLab 10.6.' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] + produces %w[application/octet-stream application/json] end get ':id/export/download' do check_rate_limit! :project_download_export, scope: [current_user, user_project.namespace] @@ -41,6 +57,16 @@ module API desc 'Start export' do detail 'This feature was introduced in GitLab 10.6.' + success code: 202 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 429, message: 'Too many requests' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end params do optional :description, type: String, desc: 'Override the project description' @@ -86,6 +112,15 @@ module API desc 'Start relations export' do detail 'This feature was introduced in GitLab 14.4' + success code: 202 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end post ':id/export_relations' do response = ::BulkImports::ExportService.new(portable: user_project, user: current_user).execute @@ -93,12 +128,23 @@ module API if response.success? accepted! else - render_api_error!(message: 'Project relations export could not be started.') + render_api_error!('Project relations export could not be started.', 500) end end desc 'Download relations export' do detail 'This feature was introduced in GitLab 14.4' + success code: 200 + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 500, message: 'Internal Server Error' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] + produces %w[application/octet-stream application/json] end params do requires :relation, @@ -119,6 +165,15 @@ module API desc 'Relations export status' do detail 'This feature was introduced in GitLab 14.4' + is_array true + success code: 200, model: Entities::BulkImports::ExportStatus + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 503, message: 'Service unavailable' } + ] + tags ['project_export'] end get ':id/export_relations/status' do present user_project.bulk_import_exports, with: Entities::BulkImports::ExportStatus diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 804cedfefe9..f2019d785a0 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -4,6 +4,8 @@ module API class SystemHooks < ::API::Base include PaginationParams + system_hooks_tags = %w[system_hooks] + feature_category :integrations before do @@ -19,12 +21,13 @@ module API end params :hook_parameters do - optional :token, type: String, desc: 'The token used to validate payloads' - optional :push_events, type: Boolean, desc: "Trigger hook on push events" - optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events" - optional :merge_requests_events, type: Boolean, desc: "Trigger hook on tag push events" - optional :repository_update_events, type: Boolean, desc: "Trigger hook on repository update events" - optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook" + optional :token, type: String, + desc: "Secret token to validate received payloads; this isn't returned in the response" + optional :push_events, type: Boolean, desc: 'When true, the hook fires on push events' + optional :tag_push_events, type: Boolean, desc: 'When true, the hook fires on new tags being pushed' + optional :merge_requests_events, type: Boolean, desc: 'Trigger hook on merge requests events' + optional :repository_update_events, type: Boolean, desc: 'Trigger hook on repository update events' + optional :enable_ssl_verification, type: Boolean, desc: 'Do SSL verification when triggering the hook' use :url_variables end end @@ -32,8 +35,11 @@ module API resource :hooks do mount ::API::Hooks::UrlVariables - desc 'Get the list of system hooks' do + desc 'List system hooks' do + detail 'Get a list of all system hooks' success Entities::Hook + is_array true + tags system_hooks_tags end params do use :pagination @@ -42,8 +48,13 @@ module API present paginate(SystemHook.all), with: Entities::Hook end - desc 'Get a hook' do + desc 'Get system hook' do + detail 'Get a system hook by its ID. Introduced in GitLab 14.9.' success Entities::Hook + failure [ + { code: 404, message: 'Not found' } + ] + tags system_hooks_tags end params do requires :hook_id, type: Integer, desc: 'The ID of the system hook' @@ -52,8 +63,15 @@ module API present find_hook, with: Entities::Hook end - desc 'Create a new system hook' do + desc 'Add new system hook' do + detail 'Add a new system hook' success Entities::Hook + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags system_hooks_tags end params do use :requires_url @@ -66,11 +84,18 @@ module API save_hook(hook, Entities::Hook) end - desc 'Update an existing system hook' do + desc 'Edit system hook' do + detail 'Edits a system hook' success Entities::Hook + failure [ + { code: 400, message: 'Validation error' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags system_hooks_tags end params do - requires :hook_id, type: Integer, desc: "The ID of the hook to update" + requires :hook_id, type: Integer, desc: 'The ID of the system hook' use :optional_url use :hook_parameters end @@ -90,8 +115,13 @@ module API kind: 'system_hooks' } - desc 'Delete a hook' do + desc 'Delete system hook' do + detail 'Deletes a system hook' success Entities::Hook + failure [ + { code: 404, message: 'Not found' } + ] + tags system_hooks_tags end params do requires :hook_id, type: Integer, desc: 'The ID of the system hook' diff --git a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml index 4d0259fe678..51bcbd278d5 100644 --- a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml @@ -12,6 +12,7 @@ stages: - test - build - deploy + - cleanup fmt: extends: .terraform:fmt diff --git a/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml index 019b970bc30..0b6c10293fc 100644 --- a/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml @@ -12,6 +12,7 @@ stages: - test - build - deploy + - cleanup fmt: extends: .terraform:fmt diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e1f184dbd36..005786d3e90 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10782,6 +10782,12 @@ msgstr "" msgid "ContributionAnalytics|No pushes for the selected time period." msgstr "" +msgid "ContributionAnalytics|The given date range is larger than 31 days" +msgstr "" + +msgid "ContributionAnalytics|The to date is earlier than the given from date" +msgstr "" + msgid "ContributionAnalytics|There is too much data to calculate. Try lowering the period_limit setting in the insights configuration file." msgstr "" diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js new file mode 100644 index 00000000000..f0fa9592419 --- /dev/null +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js @@ -0,0 +1,152 @@ +import { GlKeysetPagination } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; +import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; +import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue'; +import { packageData } from '../../mock_data'; + +describe('PackageVersionsList', () => { + let wrapper; + + const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>empty message</div>' }; + const packageList = [ + packageData({ + name: 'version 1', + }), + packageData({ + id: `gid://gitlab/Packages::Package/112`, + name: 'version 2', + }), + ]; + + const uiElements = { + findLoader: () => wrapper.findComponent(PackagesListLoader), + findListPagination: () => wrapper.findComponent(GlKeysetPagination), + findEmptySlot: () => wrapper.findComponent(EmptySlotStub), + findListRow: () => wrapper.findAllComponents(VersionRow), + }; + const mountComponent = (props) => { + wrapper = shallowMountExtended(PackageVersionsList, { + propsData: { + versions: packageList, + pageInfo: {}, + isLoading: false, + ...props, + }, + slots: { + 'empty-state': EmptySlotStub, + }, + }); + }; + + describe('when list is loading', () => { + beforeEach(() => { + mountComponent({ isLoading: true, versions: [] }); + }); + it('displays loader', () => { + expect(uiElements.findLoader().exists()).toBe(true); + }); + + it('does not display rows', () => { + expect(uiElements.findListRow().exists()).toBe(false); + }); + + it('does not display empty slot message', () => { + expect(uiElements.findEmptySlot().exists()).toBe(false); + }); + + it('does not display pagination', () => { + expect(uiElements.findListPagination().exists()).toBe(false); + }); + }); + + describe('when list is loaded and has no data', () => { + beforeEach(() => { + mountComponent({ isLoading: false, versions: [] }); + }); + + it('displays empty slot message', () => { + expect(uiElements.findEmptySlot().exists()).toBe(true); + }); + + it('does not display loader', () => { + expect(uiElements.findLoader().exists()).toBe(false); + }); + + it('does not display rows', () => { + expect(uiElements.findListRow().exists()).toBe(false); + }); + + it('does not display pagination', () => { + expect(uiElements.findListPagination().exists()).toBe(false); + }); + }); + + describe('when list is loaded with data', () => { + beforeEach(() => { + mountComponent(); + }); + + it('displays package version rows', () => { + expect(uiElements.findListRow().exists()).toEqual(true); + expect(uiElements.findListRow()).toHaveLength(packageList.length); + }); + + it('binds the correct props', () => { + expect(uiElements.findListRow().at(0).props()).toMatchObject({ + packageEntity: expect.objectContaining(packageList[0]), + }); + + expect(uiElements.findListRow().at(1).props()).toMatchObject({ + packageEntity: expect.objectContaining(packageList[1]), + }); + }); + + describe('pagination display', () => { + it('does not display pagination if there is no previous or next page', () => { + expect(uiElements.findListPagination().exists()).toBe(false); + }); + + it('displays pagination if pageInfo.hasNextPage is true', async () => { + await wrapper.setProps({ pageInfo: { hasNextPage: true } }); + expect(uiElements.findListPagination().exists()).toBe(true); + }); + + it('displays pagination if pageInfo.hasPreviousPage is true', async () => { + await wrapper.setProps({ pageInfo: { hasPreviousPage: true } }); + expect(uiElements.findListPagination().exists()).toBe(true); + }); + + it('displays pagination if both pageInfo.hasNextPage and pageInfo.hasPreviousPage are true', async () => { + await wrapper.setProps({ pageInfo: { hasNextPage: true, hasPreviousPage: true } }); + expect(uiElements.findListPagination().exists()).toBe(true); + }); + }); + + it('does not display loader', () => { + expect(uiElements.findLoader().exists()).toBe(false); + }); + + it('does not display empty slot message', () => { + expect(uiElements.findEmptySlot().exists()).toBe(false); + }); + }); + + describe('when user interacts with pagination', () => { + beforeEach(() => { + mountComponent({ pageInfo: { hasNextPage: true } }); + }); + + it('emits prev-page event when paginator emits prev event', () => { + uiElements.findListPagination().vm.$emit('prev'); + + expect(wrapper.emitted('prev-page')).toHaveLength(1); + }); + + it('emits next-page when paginator emits next event', () => { + uiElements.findListPagination().vm.$emit('next'); + + expect(wrapper.emitted('next-page')).toHaveLength(1); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js index c2b6fb734d6..f247c83c85f 100644 --- a/spec/frontend/packages_and_registries/package_registry/mock_data.js +++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js @@ -233,6 +233,12 @@ export const packageDetailsQuery = (extendPackage) => ({ }, versions: { nodes: packageVersions(), + pageInfo: { + hasNextPage: true, + hasPreviousPage: false, + endCursor: 'endCursor', + startCursor: 'startCursor', + }, __typename: 'PackageConnection', }, dependencyLinks: { diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js index a32e76a132e..f942a334f40 100644 --- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js @@ -15,8 +15,8 @@ import InstallationCommands from '~/packages_and_registries/package_registry/com import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue'; import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue'; -import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue'; import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue'; +import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue'; import { FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, PACKAGE_TYPE_COMPOSER, @@ -99,6 +99,7 @@ describe('PackagesApp', () => { GlSprintf, GlTabs, GlTab, + PackageVersionsList, }, mocks: { $route: { @@ -120,8 +121,7 @@ describe('PackagesApp', () => { const findPackageFiles = () => wrapper.findComponent(PackageFiles); const findDeleteFileModal = () => wrapper.findByTestId('delete-file-modal'); const findDeleteFilesModal = () => wrapper.findByTestId('delete-files-modal'); - const findVersionRows = () => wrapper.findAllComponents(VersionRow); - const noVersionsMessage = () => wrapper.findByTestId('no-versions-message'); + const findVersionsList = () => wrapper.findComponent(PackageVersionsList); const findDependenciesCountBadge = () => wrapper.findComponent(GlBadge); const findNoDependenciesMessage = () => wrapper.findByTestId('no-dependencies-message'); const findDependencyRows = () => wrapper.findAllComponents(DependencyRow); @@ -558,38 +558,23 @@ describe('PackagesApp', () => { }); describe('versions', () => { - it('displays the correct version count when the package has versions', async () => { + it('displays versions list when the package has versions', async () => { createComponent(); await waitForPromises(); - expect(findVersionRows()).toHaveLength(packageVersions().length); + expect(findVersionsList()).toBeDefined(); }); it('binds the correct props', async () => { - const [versionPackage] = packageVersions(); - // eslint-disable-next-line no-underscore-dangle - delete versionPackage.__typename; - delete versionPackage.tags; - - createComponent(); - + const versionNodes = packageVersions(); + createComponent({ packageEntity: { versions: { nodes: versionNodes } } }); await waitForPromises(); - expect(findVersionRows().at(0).props()).toMatchObject({ - packageEntity: expect.objectContaining(versionPackage), + expect(findVersionsList().props()).toMatchObject({ + versions: expect.arrayContaining(versionNodes), }); }); - - it('displays the no versions message when there are none', async () => { - createComponent({ - resolver: jest.fn().mockResolvedValue(packageDetailsQuery({ versions: { nodes: [] } })), - }); - - await waitForPromises(); - - expect(noVersionsMessage().exists()).toBe(true); - }); }); describe('dependency links', () => { it('does not show the dependency links for a non nuget package', async () => { diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 10931261c4f..78c0d0a2b11 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -539,4 +539,42 @@ RSpec.describe DiffHelper do end end end + + describe '#show_only_context_commits?' do + let(:params) { {} } + let(:merge_request) { build_stubbed(:merge_request) } + let(:has_no_commits) { true } + + subject(:result) { helper.show_only_context_commits? } + + before do + assign(:merge_request, merge_request) + allow(helper).to receive(:params).and_return(params) + allow(merge_request).to receive(:has_no_commits?).and_return(has_no_commits) + end + + context 'when only_context_commits param is set to true' do + let(:params) { { only_context_commits: true } } + + it { is_expected.to be_truthy } + + context 'when merge request has commits' do + let(:has_no_commits) { false } + + it { is_expected.to be_truthy } + end + end + + context 'when only_context_commits param is set to false' do + let(:params) { { only_context_commits: false } } + + it { is_expected.to be_truthy } + + context 'when merge request has commits' do + let(:has_no_commits) { false } + + it { is_expected.to be_falsey } + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 235342b00c9..9d169c50013 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -78,7 +78,6 @@ require_relative '../tooling/quality/test_level' quality_level = Quality::TestLevel.new RSpec.configure do |config| - config.threadsafe = false config.use_transactional_fixtures = true config.use_instantiated_fixtures = false config.fixture_path = Rails.root diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb index 6795d2f6d2a..71dfc3fd5a3 100644 --- a/spec/support/rspec.rb +++ b/spec/support/rspec.rb @@ -11,6 +11,9 @@ require_relative "helpers/fast_rails_root" RSpec::Expectations.configuration.on_potential_false_positives = :raise RSpec.configure do |config| + # See https://gitlab.com/gitlab-org/gitlab/-/issues/379686 + config.threadsafe = false + # Re-run failures locally with `--only-failures` config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt') |