Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-07 09:08:11 +00:00
parent 44c74f7b06
commit bc9a474793
31 changed files with 651 additions and 89 deletions

View file

@ -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>

View file

@ -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 {

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1 @@
167032d562467c3d6be9e6c6c8c072f117e23798db35301f95386130ae115a00

View file

@ -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);

View file

@ -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.

View file

@ -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.

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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. |

View file

@ -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).

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 } }

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -12,6 +12,7 @@ stages:
- test
- build
- deploy
- cleanup
fmt:
extends: .terraform:fmt

View file

@ -12,6 +12,7 @@ stages:
- test
- build
- deploy
- cleanup
fmt:
extends: .terraform:fmt

View file

@ -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 ""

View file

@ -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);
});
});
});

View file

@ -233,6 +233,12 @@ export const packageDetailsQuery = (extendPackage) => ({
},
versions: {
nodes: packageVersions(),
pageInfo: {
hasNextPage: true,
hasPreviousPage: false,
endCursor: 'endCursor',
startCursor: 'startCursor',
},
__typename: 'PackageConnection',
},
dependencyLinks: {

View file

@ -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 () => {

View file

@ -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

View file

@ -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

View file

@ -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')