Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6be8ed5a95
commit
c74f702c74
|
@ -1 +1 @@
|
||||||
7c2fcde23bd4a962409897adbbb71da11c6db99a
|
a31bd1be25d0ff03efaa7f756321ea9440122b24
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { __ } from '~/locale';
|
||||||
import Tracking from '~/tracking';
|
import Tracking from '~/tracking';
|
||||||
import {
|
import {
|
||||||
NOT_ENOUGH_DATA_ERROR,
|
NOT_ENOUGH_DATA_ERROR,
|
||||||
|
FIELD_KEY_TITLE,
|
||||||
PAGINATION_SORT_FIELD_END_EVENT,
|
PAGINATION_SORT_FIELD_END_EVENT,
|
||||||
PAGINATION_SORT_FIELD_DURATION,
|
PAGINATION_SORT_FIELD_DURATION,
|
||||||
PAGINATION_SORT_DIRECTION_ASC,
|
PAGINATION_SORT_DIRECTION_ASC,
|
||||||
|
@ -22,7 +23,8 @@ import TotalTime from './total_time.vue';
|
||||||
|
|
||||||
const DEFAULT_WORKFLOW_TITLE_PROPERTIES = {
|
const DEFAULT_WORKFLOW_TITLE_PROPERTIES = {
|
||||||
thClass: 'gl-w-half',
|
thClass: 'gl-w-half',
|
||||||
key: PAGINATION_SORT_FIELD_END_EVENT,
|
key: FIELD_KEY_TITLE,
|
||||||
|
sortable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const WORKFLOW_COLUMN_TITLES = {
|
const WORKFLOW_COLUMN_TITLES = {
|
||||||
|
@ -132,14 +134,16 @@ export default {
|
||||||
return [
|
return [
|
||||||
this.workflowTitle,
|
this.workflowTitle,
|
||||||
{
|
{
|
||||||
key: PAGINATION_SORT_FIELD_DURATION,
|
key: PAGINATION_SORT_FIELD_END_EVENT,
|
||||||
label: __('Time'),
|
label: __('Last event'),
|
||||||
thClass: 'gl-w-half',
|
sortable: this.sortable,
|
||||||
},
|
},
|
||||||
].map((field) => ({
|
{
|
||||||
...field,
|
key: PAGINATION_SORT_FIELD_DURATION,
|
||||||
sortable: this.sortable,
|
label: __('Duration'),
|
||||||
}));
|
sortable: this.sortable,
|
||||||
|
},
|
||||||
|
];
|
||||||
},
|
},
|
||||||
prevPage() {
|
prevPage() {
|
||||||
return Math.max(this.pagination.page - 1, 0);
|
return Math.max(this.pagination.page - 1, 0);
|
||||||
|
@ -201,7 +205,7 @@ export default {
|
||||||
:empty-text="emptyStateMessage"
|
:empty-text="emptyStateMessage"
|
||||||
@sort-changed="onSort"
|
@sort-changed="onSort"
|
||||||
>
|
>
|
||||||
<template v-if="stageCount" #head(end_event)="data">
|
<template v-if="stageCount" #head(title)="data">
|
||||||
<span>{{ data.label }}</span
|
<span>{{ data.label }}</span
|
||||||
><gl-badge class="gl-ml-2" size="sm"
|
><gl-badge class="gl-ml-2" size="sm"
|
||||||
><formatted-stage-count :stage-count="stageCount"
|
><formatted-stage-count :stage-count="stageCount"
|
||||||
|
@ -210,7 +214,10 @@ export default {
|
||||||
<template #head(duration)="data">
|
<template #head(duration)="data">
|
||||||
<span data-testid="vsa-stage-header-duration">{{ data.label }}</span>
|
<span data-testid="vsa-stage-header-duration">{{ data.label }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #cell(end_event)="{ item }">
|
<template #head(end_event)="data">
|
||||||
|
<span data-testid="vsa-stage-header-last-event">{{ data.label }}</span>
|
||||||
|
</template>
|
||||||
|
<template #cell(title)="{ item }">
|
||||||
<div data-testid="vsa-stage-event">
|
<div data-testid="vsa-stage-event">
|
||||||
<div v-if="item.id" data-testid="vsa-stage-content">
|
<div v-if="item.id" data-testid="vsa-stage-content">
|
||||||
<p class="gl-m-0">
|
<p class="gl-m-0">
|
||||||
|
@ -282,6 +289,9 @@ export default {
|
||||||
<template #cell(duration)="{ item }">
|
<template #cell(duration)="{ item }">
|
||||||
<total-time :time="item.totalTime" data-testid="vsa-stage-event-time" />
|
<total-time :time="item.totalTime" data-testid="vsa-stage-event-time" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #cell(end_event)="{ item }">
|
||||||
|
<span data-testid="vsa-stage-last-event">{{ item.endEventTimestamp }}</span>
|
||||||
|
</template>
|
||||||
</gl-table>
|
</gl-table>
|
||||||
<gl-pagination
|
<gl-pagination
|
||||||
v-if="pagination && !isLoading && !isEmptyStage"
|
v-if="pagination && !isLoading && !isEmptyStage"
|
||||||
|
|
|
@ -22,6 +22,7 @@ export const PAGINATION_SORT_FIELD_END_EVENT = 'end_event';
|
||||||
export const PAGINATION_SORT_FIELD_DURATION = 'duration';
|
export const PAGINATION_SORT_FIELD_DURATION = 'duration';
|
||||||
export const PAGINATION_SORT_DIRECTION_DESC = 'desc';
|
export const PAGINATION_SORT_DIRECTION_DESC = 'desc';
|
||||||
export const PAGINATION_SORT_DIRECTION_ASC = 'asc';
|
export const PAGINATION_SORT_DIRECTION_ASC = 'asc';
|
||||||
|
export const FIELD_KEY_TITLE = 'title';
|
||||||
|
|
||||||
export const I18N_VSA_ERROR_STAGES = __(
|
export const I18N_VSA_ERROR_STAGES = __(
|
||||||
'There was an error fetching value stream analytics stages.',
|
'There was an error fetching value stream analytics stages.',
|
||||||
|
|
|
@ -64,9 +64,10 @@ export default {
|
||||||
v-if="isActive"
|
v-if="isActive"
|
||||||
name="arrow-right"
|
name="arrow-right"
|
||||||
class="icon-arrow-right gl-absolute gl-display-block"
|
class="icon-arrow-right gl-absolute gl-display-block"
|
||||||
|
:size="14"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ci-icon :status="job.status" />
|
<ci-icon :status="job.status" class="gl-mr-2" :size="14" />
|
||||||
|
|
||||||
<span class="gl-text-truncate gl-w-full">{{ jobName }}</span>
|
<span class="gl-text-truncate gl-w-full">{{ jobName }}</span>
|
||||||
|
|
||||||
|
|
|
@ -170,12 +170,6 @@
|
||||||
width: 289px;
|
width: 289px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
svg {
|
|
||||||
margin-right: 3px;
|
|
||||||
height: 14px;
|
|
||||||
width: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
padding: $gl-padding 10px $gl-padding 40px;
|
padding: $gl-padding 10px $gl-padding 40px;
|
||||||
width: 270px;
|
width: 270px;
|
||||||
|
|
|
@ -78,6 +78,7 @@ module ContainerRegistry
|
||||||
return unless project
|
return unless project
|
||||||
return unless Feature.enabled?(:container_registry_project_statistics, project)
|
return unless Feature.enabled?(:container_registry_project_statistics, project)
|
||||||
|
|
||||||
|
Rails.cache.delete(project.root_ancestor.container_repositories_size_cache_key)
|
||||||
ProjectCacheWorker.perform_async(project.id, [], [:container_registry_size])
|
ProjectCacheWorker.perform_async(project.id, [], [:container_registry_size])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -863,6 +863,12 @@ class Group < Namespace
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def gitlab_deploy_token
|
||||||
|
strong_memoize(:gitlab_deploy_token) do
|
||||||
|
deploy_tokens.gitlab_deploy_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def feature_flag_enabled_for_self_or_ancestor?(feature_flag)
|
def feature_flag_enabled_for_self_or_ancestor?(feature_flag)
|
||||||
|
|
|
@ -427,14 +427,21 @@ class Namespace < ApplicationRecord
|
||||||
aggregation_schedule.present?
|
aggregation_schedule.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def container_repositories_size_cache_key
|
||||||
|
"namespaces:#{id}:container_repositories_size"
|
||||||
|
end
|
||||||
|
|
||||||
def container_repositories_size
|
def container_repositories_size
|
||||||
strong_memoize(:container_repositories_size) do
|
strong_memoize(:container_repositories_size) do
|
||||||
next unless Gitlab.com?
|
next unless Gitlab.com?
|
||||||
|
next unless root?
|
||||||
next unless ContainerRegistry::GitlabApiClient.supports_gitlab_api?
|
next unless ContainerRegistry::GitlabApiClient.supports_gitlab_api?
|
||||||
next 0 if all_container_repositories.empty?
|
next 0 if all_container_repositories.empty?
|
||||||
next unless all_container_repositories.all_migrated?
|
next unless all_container_repositories.all_migrated?
|
||||||
|
|
||||||
ContainerRegistry::GitlabApiClient.deduplicated_size(full_path)
|
Rails.cache.fetch(container_repositories_size_cache_key, expires_in: 7.days) do
|
||||||
|
ContainerRegistry::GitlabApiClient.deduplicated_size(full_path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,9 @@ class PoolRepository < ApplicationRecord
|
||||||
object_pool.link(repository.raw)
|
object_pool.link(repository.raw)
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_obsolete_if_last(repository)
|
def unlink_repository(repository)
|
||||||
|
repository.disconnect_alternates
|
||||||
|
|
||||||
if member_projects.where.not(id: repository.project.id).exists?
|
if member_projects.where.not(id: repository.project.id).exists?
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
|
|
|
@ -2509,7 +2509,13 @@ class Project < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def gitlab_deploy_token
|
def gitlab_deploy_token
|
||||||
@gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token
|
strong_memoize(:gitlab_deploy_token) do
|
||||||
|
if Feature.enabled?(:ci_variable_for_group_gitlab_deploy_token, self)
|
||||||
|
deploy_tokens.gitlab_deploy_token || group&.gitlab_deploy_token
|
||||||
|
else
|
||||||
|
deploy_tokens.gitlab_deploy_token
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def any_lfs_file_locks?
|
def any_lfs_file_locks?
|
||||||
|
@ -2566,7 +2572,7 @@ class Project < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def leave_pool_repository
|
def leave_pool_repository
|
||||||
pool_repository&.mark_obsolete_if_last(repository) && update_column(:pool_repository_id, nil)
|
pool_repository&.unlink_repository(repository) && update_column(:pool_repository_id, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_pool_repository
|
def link_pool_repository
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: ci_variable_for_group_gitlab_deploy_token
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88696
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363621
|
||||||
|
milestone: '15.1'
|
||||||
|
type: development
|
||||||
|
group: group::pipeline authoring
|
||||||
|
default_enabled: false
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddLicenseUsageDataExportedToApplicationSettings < Gitlab::Database::Migration[2.0]
|
||||||
|
enable_lock_retries!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :application_settings, :license_usage_data_exported, :boolean, default: false, null: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
55c13dd2cf8db2ca54d3fb1bd09d459e90a90e01b3c1f7ad950e4b618df241af
|
|
@ -11319,6 +11319,7 @@ CREATE TABLE application_settings (
|
||||||
jira_connect_application_key text,
|
jira_connect_application_key text,
|
||||||
globally_allowed_ips text DEFAULT ''::text NOT NULL,
|
globally_allowed_ips text DEFAULT ''::text NOT NULL,
|
||||||
container_registry_pre_import_tags_rate numeric(6,2) DEFAULT 0.5 NOT NULL,
|
container_registry_pre_import_tags_rate numeric(6,2) DEFAULT 0.5 NOT NULL,
|
||||||
|
license_usage_data_exported boolean DEFAULT false NOT NULL,
|
||||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||||
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
|
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
|
||||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||||
|
|
|
@ -898,6 +898,44 @@ def down
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dropping a sequence
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88387) in GitLab 15.1.
|
||||||
|
|
||||||
|
Dropping a sequence is uncommon, but you can use the `drop_sequence` method provided by the database team.
|
||||||
|
|
||||||
|
Under the hood, it works like this:
|
||||||
|
|
||||||
|
Remove a sequence:
|
||||||
|
|
||||||
|
- Remove the default value if the sequence is actually used.
|
||||||
|
- Execute `DROP SEQUENCE`.
|
||||||
|
|
||||||
|
Re-add a sequence:
|
||||||
|
|
||||||
|
- Create the sequence, with the possibility of specifying the current value.
|
||||||
|
- Change the default value of the column.
|
||||||
|
|
||||||
|
A Rails migration example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class DropSequenceTest < Gitlab::Database::Migration[2.0]
|
||||||
|
def up
|
||||||
|
drop_sequence(:ci_pipelines_config, :pipeline_id, :ci_pipelines_config_pipeline_id_seq)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
default_value = Ci::Pipeline.maximum(:id) + 10_000
|
||||||
|
|
||||||
|
add_sequence(:ci_pipelines_config, :pipeline_id, :ci_pipelines_config_pipeline_id_seq, default_value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
`add_sequence` should be avoided for columns with foreign keys.
|
||||||
|
Adding sequence to these columns is **only allowed** in the down method (restore previous schema state).
|
||||||
|
|
||||||
## Integer column type
|
## Integer column type
|
||||||
|
|
||||||
By default, an integer column can hold up to a 4-byte (32-bit) number. That is
|
By default, an integer column can hold up to a 4-byte (32-bit) number. That is
|
||||||
|
|
|
@ -43,8 +43,7 @@ To view value stream analytics for your project:
|
||||||
- In the **From** field, select a start date.
|
- In the **From** field, select a start date.
|
||||||
- In the **To** field, select an end date.
|
- In the **To** field, select an end date.
|
||||||
1. Optional. Sort results by ascending or descending:
|
1. Optional. Sort results by ascending or descending:
|
||||||
- To sort by most recent or oldest workflow item, select the **Merge requests** or **Issues**
|
- To sort by most recent or oldest workflow item, select the **Last event** header.
|
||||||
header. The header name differs based on the stage you select.
|
|
||||||
- To sort by most or least amount of time spent in each stage, select the **Time** header.
|
- To sort by most or least amount of time spent in each stage, select the **Time** header.
|
||||||
|
|
||||||
The table shows a list of related workflow items for the selected stage. Based on the stage you choose, this can be:
|
The table shows a list of related workflow items for the selected stage. Based on the stage you choose, this can be:
|
||||||
|
|
|
@ -50,8 +50,7 @@ To view value stream analytics for your group:
|
||||||
- In the **To** field, select an end date. The charts and list show workflow items created
|
- In the **To** field, select an end date. The charts and list show workflow items created
|
||||||
during the date range.
|
during the date range.
|
||||||
1. Optional. Sort results by ascending or descending:
|
1. Optional. Sort results by ascending or descending:
|
||||||
- To sort by most recent or oldest workflow item, select the **Merge requests** or **Issues**
|
- To sort by most recent or oldest workflow item, select the **Last event** header.
|
||||||
header. The header name differs based on the stage you select.
|
|
||||||
- To sort by most or least amount of time spent in each stage, select the **Time** header.
|
- To sort by most or least amount of time spent in each stage, select the **Time** header.
|
||||||
|
|
||||||
A badge next to the workflow items table header shows the number of workflow items that
|
A badge next to the workflow items table header shows the number of workflow items that
|
||||||
|
|
|
@ -190,6 +190,8 @@ To pull images from the Dependency Proxy, you must:
|
||||||
|
|
||||||
### GitLab deploy token
|
### GitLab deploy token
|
||||||
|
|
||||||
|
> Support for `gitlab-deploy-token` at the group level [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214014) in GitLab 15.1 [with a flag](../../../administration/feature_flags.md) named `ci_variable_for_group_gitlab_deploy_token`. Disabled by default.
|
||||||
|
|
||||||
There's a special case when it comes to deploy tokens. If a user creates one
|
There's a special case when it comes to deploy tokens. If a user creates one
|
||||||
named `gitlab-deploy-token`, the username and token of the deploy token is
|
named `gitlab-deploy-token`, the username and token of the deploy token is
|
||||||
automatically exposed to the CI/CD jobs as CI/CD variables: `CI_DEPLOY_USER`
|
automatically exposed to the CI/CD jobs as CI/CD variables: `CI_DEPLOY_USER`
|
||||||
|
@ -203,9 +205,10 @@ docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
|
||||||
```
|
```
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
The special handling for the `gitlab-deploy-token` deploy token is not
|
In GitLab 15.0 and earlier, the special handling for the `gitlab-deploy-token` deploy token
|
||||||
implemented for group deploy tokens. To make the group-level deploy token available for
|
does not work for group deploy tokens. To make the group-level deploy token available
|
||||||
CI/CD jobs, the `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD` variables should be set under **Settings** to the name and token of the group deploy token respectively.
|
for CI/CD jobs, the `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD` CI/CD variables must be
|
||||||
|
set in **Settings > CI/CD > Variables** to the name and token of the group deploy token.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
|
@ -394,3 +394,27 @@ run tests:
|
||||||
coverage_format: cobertura
|
coverage_format: cobertura
|
||||||
path: coverage/coverage.xml
|
path: coverage/coverage.xml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Test coverage visualization not displayed
|
||||||
|
|
||||||
|
If the test coverage visualization is not displayed in the diff view, you can check
|
||||||
|
the coverage report itself and verify that:
|
||||||
|
|
||||||
|
- The file you are viewing in the diff view is mentioned in the coverage report.
|
||||||
|
- The `source` and `filename` nodes in the report follows the [expected structure](#automatic-class-path-correction)
|
||||||
|
to match the files in your repository.
|
||||||
|
|
||||||
|
Report artifacts are not downloadable by default. If you want the report to be downloadable
|
||||||
|
from the job details page, add your coverage report to the artifact `paths`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage/cobertura-coverage.xml
|
||||||
|
reports:
|
||||||
|
coverage_report:
|
||||||
|
coverage_format: cobertura
|
||||||
|
path: coverage/cobertura-coverage.xml
|
||||||
|
```
|
||||||
|
|
|
@ -1499,6 +1499,20 @@ into similar problems in the future (e.g. when new tables are created).
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def drop_sequence(table_name, column_name, sequence_name)
|
||||||
|
execute <<~SQL
|
||||||
|
ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} DROP DEFAULT;
|
||||||
|
DROP SEQUENCE IF EXISTS #{quote_table_name(sequence_name)}
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_sequence(table_name, column_name, sequence_name, start_value)
|
||||||
|
execute <<~SQL
|
||||||
|
CREATE SEQUENCE #{quote_table_name(sequence_name)} START #{start_value};
|
||||||
|
ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT nextval(#{quote(sequence_name)})
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_temporary_columns_and_triggers(table, columns, primary_key: :id, data_type: :bigint)
|
def create_temporary_columns_and_triggers(table, columns, primary_key: :id, data_type: :bigint)
|
||||||
|
|
|
@ -22362,6 +22362,9 @@ msgstr ""
|
||||||
msgid "Last edited by %{link_start}%{avatar} %{name}%{link_end}"
|
msgid "Last edited by %{link_start}%{avatar} %{name}%{link_end}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Last event"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Last item before this page loaded in your browser:"
|
msgid "Last item before this page loaded in your browser:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -33858,9 +33861,6 @@ msgstr ""
|
||||||
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
|
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "SecurityOrchestration|Group level policy"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "SecurityOrchestration|If any scanner finds a newly detected critical vulnerability in an open merge request targeting the master branch, then require two approvals from any member of App security."
|
msgid "SecurityOrchestration|If any scanner finds a newly detected critical vulnerability in an open merge request targeting the master branch, then require two approvals from any member of App security."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ const findTableHeadColumns = () => findTableHead().findAll('th');
|
||||||
const findStageEventTitle = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-title');
|
const findStageEventTitle = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-title');
|
||||||
const findStageEventLink = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-link');
|
const findStageEventLink = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-link');
|
||||||
const findStageTime = () => wrapper.findByTestId('vsa-stage-event-time');
|
const findStageTime = () => wrapper.findByTestId('vsa-stage-event-time');
|
||||||
|
const findStageLastEvent = () => wrapper.findByTestId('vsa-stage-last-event');
|
||||||
const findIcon = (name) => wrapper.findByTestId(`${name}-icon`);
|
const findIcon = (name) => wrapper.findByTestId(`${name}-icon`);
|
||||||
|
|
||||||
function createComponent(props = {}, shallow = false) {
|
function createComponent(props = {}, shallow = false) {
|
||||||
|
@ -128,6 +129,10 @@ describe('StageTable', () => {
|
||||||
expect(findStageTime().text()).toBe(createdAt);
|
expect(findStageTime().text()).toBe(createdAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('will render the end event', () => {
|
||||||
|
expect(findStageLastEvent().text()).toBe(firstIssueEvent.endEventTimestamp);
|
||||||
|
});
|
||||||
|
|
||||||
it('will render the author', () => {
|
it('will render the author', () => {
|
||||||
expect(wrapper.findByTestId('vsa-stage-event-author').text()).toContain(
|
expect(wrapper.findByTestId('vsa-stage-event-author').text()).toContain(
|
||||||
firstIssueEvent.author.name,
|
firstIssueEvent.author.name,
|
||||||
|
@ -303,10 +308,20 @@ describe('StageTable', () => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can sort the table by each column', () => {
|
it('can sort the end event or duration', () => {
|
||||||
findTableHeadColumns().wrappers.forEach((w) => {
|
findTableHeadColumns()
|
||||||
expect(w.attributes('aria-sort')).toBe('none');
|
.wrappers.slice(1)
|
||||||
});
|
.forEach((w) => {
|
||||||
|
expect(w.attributes('aria-sort')).toBe('none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot be sorted by title', () => {
|
||||||
|
findTableHeadColumns()
|
||||||
|
.wrappers.slice(0, 1)
|
||||||
|
.forEach((w) => {
|
||||||
|
expect(w.attributes('aria-sort')).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clicking a table column will send tracking information', () => {
|
it('clicking a table column will send tracking information', () => {
|
||||||
|
|
|
@ -3281,4 +3281,20 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
||||||
model.rename_constraint(:test_table, :fk_old_name, :fk_new_name)
|
model.rename_constraint(:test_table, :fk_old_name, :fk_new_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#drop_sequence' do
|
||||||
|
it "executes the statement to drop the sequence" do
|
||||||
|
expect(model).to receive(:execute).with /ALTER TABLE "test_table" ALTER COLUMN "test_column" DROP DEFAULT;\nDROP SEQUENCE IF EXISTS "test_table_id_seq"/
|
||||||
|
|
||||||
|
model.drop_sequence(:test_table, :test_column, :test_table_id_seq)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#add_sequence' do
|
||||||
|
it "executes the statement to add the sequence" do
|
||||||
|
expect(model).to receive(:execute).with "CREATE SEQUENCE \"test_table_id_seq\" START 1;\nALTER TABLE \"test_table\" ALTER COLUMN \"test_column\" SET DEFAULT nextval(\'test_table_id_seq\')\n"
|
||||||
|
|
||||||
|
model.add_sequence(:test_table, :test_column, :test_table_id_seq, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3538,7 +3538,7 @@ RSpec.describe Ci::Build do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when gitlab-deploy-token exists' do
|
context 'when gitlab-deploy-token exists for project' do
|
||||||
before do
|
before do
|
||||||
project.deploy_tokens << deploy_token
|
project.deploy_tokens << deploy_token
|
||||||
end
|
end
|
||||||
|
@ -3548,11 +3548,32 @@ RSpec.describe Ci::Build do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when gitlab-deploy-token does not exist' do
|
context 'when gitlab-deploy-token does not exist for project' do
|
||||||
it 'does not include deploy token variables' do
|
it 'does not include deploy token variables' do
|
||||||
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil
|
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil
|
||||||
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil
|
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when gitlab-deploy-token exists for group' do
|
||||||
|
before do
|
||||||
|
group.deploy_tokens << deploy_token
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes deploy token variables' do
|
||||||
|
is_expected.to include(*deploy_token_variables)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the FF ci_variable_for_group_gitlab_deploy_token is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(ci_variable_for_group_gitlab_deploy_token: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not include deploy token variables' do
|
||||||
|
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil
|
||||||
|
expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,12 @@ RSpec.describe ContainerRegistry::Event do
|
||||||
handle!
|
handle!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'clears the cache for the namespace container repositories size' do
|
||||||
|
expect(Rails.cache).to receive(:delete).with(group.container_repositories_size_cache_key)
|
||||||
|
|
||||||
|
handle!
|
||||||
|
end
|
||||||
|
|
||||||
shared_examples 'event without project statistics update' do
|
shared_examples 'event without project statistics update' do
|
||||||
it 'does not queue a project statistics update' do
|
it 'does not queue a project statistics update' do
|
||||||
expect(ProjectCacheWorker).not_to receive(:perform_async)
|
expect(ProjectCacheWorker).not_to receive(:perform_async)
|
||||||
|
|
|
@ -3396,4 +3396,42 @@ RSpec.describe Group do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#gitlab_deploy_token' do
|
||||||
|
subject(:gitlab_deploy_token) { group.gitlab_deploy_token }
|
||||||
|
|
||||||
|
context 'when there is a gitlab deploy token associated' do
|
||||||
|
let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, groups: [group]) }
|
||||||
|
|
||||||
|
it { is_expected.to eq(deploy_token) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is no a gitlab deploy token associated' do
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a gitlab deploy token associated but is has been revoked' do
|
||||||
|
let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, :revoked, groups: [group]) }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a gitlab deploy token associated but it is expired' do
|
||||||
|
let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, :expired, groups: [group]) }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a deploy token associated with a different name' do
|
||||||
|
let!(:deploy_token) { create(:deploy_token, :group, groups: [group]) }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a gitlab deploy token associated to a different group' do
|
||||||
|
let!(:deploy_token) { create(:deploy_token, :group, :gitlab_deploy_token, groups: [create(:group)]) }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -583,7 +583,13 @@ RSpec.describe Namespace do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#container_repositories_size' do
|
describe '#container_repositories_size_cache_key' do
|
||||||
|
it 'returns the correct cache key' do
|
||||||
|
expect(namespace.container_repositories_size_cache_key).to eq "namespaces:#{namespace.id}:container_repositories_size"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#container_repositories_size', :clean_gitlab_redis_cache do
|
||||||
let(:project_namespace) { create(:namespace) }
|
let(:project_namespace) { create(:namespace) }
|
||||||
|
|
||||||
subject { project_namespace.container_repositories_size }
|
subject { project_namespace.container_repositories_size }
|
||||||
|
@ -611,12 +617,29 @@ RSpec.describe Namespace do
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq(expected_result) }
|
it { is_expected.to eq(expected_result) }
|
||||||
|
|
||||||
|
it 'caches the result when all migrated' do
|
||||||
|
if all_migrated
|
||||||
|
expect(Rails.cache)
|
||||||
|
.to receive(:fetch)
|
||||||
|
.with(project_namespace.container_repositories_size_cache_key, expires_in: 7.days)
|
||||||
|
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'not on gitlab.com' do
|
context 'not on gitlab.com' do
|
||||||
it { is_expected.to eq(nil) }
|
it { is_expected.to eq(nil) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'for a sub-group' do
|
||||||
|
let(:parent_namespace) { create(:group) }
|
||||||
|
let(:project_namespace) { create(:group, parent: parent_namespace) }
|
||||||
|
|
||||||
|
it { is_expected.to eq(nil) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#all_container_repositories' do
|
describe '#all_container_repositories' do
|
||||||
|
|
|
@ -24,23 +24,35 @@ RSpec.describe PoolRepository do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#mark_obsolete_if_last' do
|
describe '#unlink_repository' do
|
||||||
let(:pool) { create(:pool_repository, :ready) }
|
let(:pool) { create(:pool_repository, :ready) }
|
||||||
|
let(:repository_path) { File.join(TestEnv.repos_path, pool.source_project.repository.relative_path) }
|
||||||
|
let(:alternates_file) { File.join(repository_path, 'objects', 'info', 'alternates') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
pool.link_repository(pool.source_project.repository)
|
||||||
|
end
|
||||||
|
|
||||||
context 'when the last member leaves' do
|
context 'when the last member leaves' do
|
||||||
it 'schedules pool removal' do
|
it 'schedules pool removal' do
|
||||||
expect(::ObjectPool::DestroyWorker).to receive(:perform_async).with(pool.id).and_call_original
|
expect(::ObjectPool::DestroyWorker).to receive(:perform_async).with(pool.id).and_call_original
|
||||||
|
|
||||||
pool.mark_obsolete_if_last(pool.source_project.repository)
|
pool.unlink_repository(pool.source_project.repository)
|
||||||
|
|
||||||
|
expect(File).not_to exist(alternates_file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the second member leaves' do
|
context 'when the second member leaves' do
|
||||||
it 'does not schedule pool removal' do
|
it 'does not schedule pool removal' do
|
||||||
create(:project, :repository, pool_repository: pool)
|
other_project = create(:project, :repository, pool_repository: pool)
|
||||||
|
pool.link_repository(other_project.repository)
|
||||||
|
|
||||||
expect(::ObjectPool::DestroyWorker).not_to receive(:perform_async).with(pool.id)
|
expect(::ObjectPool::DestroyWorker).not_to receive(:perform_async).with(pool.id)
|
||||||
|
|
||||||
pool.mark_obsolete_if_last(pool.source_project.repository)
|
pool.unlink_repository(pool.source_project.repository)
|
||||||
|
|
||||||
|
expect(File).not_to exist(alternates_file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6220,7 +6220,7 @@ RSpec.describe Project, factory_default: :keep do
|
||||||
describe '#gitlab_deploy_token' do
|
describe '#gitlab_deploy_token' do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
|
|
||||||
subject { project.gitlab_deploy_token }
|
subject(:gitlab_deploy_token) { project.gitlab_deploy_token }
|
||||||
|
|
||||||
context 'when there is a gitlab deploy token associated' do
|
context 'when there is a gitlab deploy token associated' do
|
||||||
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }
|
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }
|
||||||
|
@ -6252,10 +6252,43 @@ RSpec.describe Project, factory_default: :keep do
|
||||||
|
|
||||||
context 'when there is a deploy token associated to a different project' do
|
context 'when there is a deploy token associated to a different project' do
|
||||||
let(:project_2) { create(:project) }
|
let(:project_2) { create(:project) }
|
||||||
let!(:deploy_token) { create(:deploy_token, projects: [project_2]) }
|
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project_2]) }
|
||||||
|
|
||||||
it { is_expected.to be_nil }
|
it { is_expected.to be_nil }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the project group has a gitlab deploy token associated' do
|
||||||
|
let(:group) { create(:group) }
|
||||||
|
let(:project) { create(:project, group: group) }
|
||||||
|
let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :group, groups: [group]) }
|
||||||
|
|
||||||
|
it { is_expected.to eq(deploy_token) }
|
||||||
|
|
||||||
|
context 'when the FF ci_variable_for_group_gitlab_deploy_token is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(ci_variable_for_group_gitlab_deploy_token: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the project and its group has a gitlab deploy token associated' do
|
||||||
|
let(:group) { create(:group) }
|
||||||
|
let(:project) { create(:project, group: group) }
|
||||||
|
let!(:project_deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }
|
||||||
|
let!(:group_deploy_token) { create(:deploy_token, :gitlab_deploy_token, :group, groups: [group]) }
|
||||||
|
|
||||||
|
it { is_expected.to eq(project_deploy_token) }
|
||||||
|
|
||||||
|
context 'when the FF ci_variable_for_group_gitlab_deploy_token is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(ci_variable_for_group_gitlab_deploy_token: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(project_deploy_token) }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with uploads' do
|
context 'with uploads' do
|
||||||
|
|
|
@ -14,20 +14,7 @@ RSpec.describe Projects::EnvironmentsController do
|
||||||
sign_in(project.owner)
|
sign_in(project.owner)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'avoids N+1 queries', :use_sql_query_cache do
|
include_examples 'avoids N+1 queries on environment detail page'
|
||||||
create_deployment_with_associations(commit_depth: 19)
|
|
||||||
|
|
||||||
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
|
|
||||||
get project_environment_path(project, environment), params: environment_params
|
|
||||||
end
|
|
||||||
|
|
||||||
18.downto(0).each { |n| create_deployment_with_associations(commit_depth: n) }
|
|
||||||
|
|
||||||
# N+1s exist for loading commit emails and users
|
|
||||||
expect do
|
|
||||||
get project_environment_path(project, environment), params: environment_params
|
|
||||||
end.not_to exceed_all_query_limit(control).with_threshold(9)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def environment_params(opts = {})
|
def environment_params(opts = {})
|
||||||
|
|
|
@ -65,20 +65,3 @@ RSpec.shared_examples 'failed response for #cancel_auto_stop' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
RSpec.shared_examples 'avoids N+1 queries on environment detail page' do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
before do
|
|
||||||
create_deployment_with_associations(sequence: 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'avoids N+1 queries' do
|
|
||||||
control = ActiveRecord::QueryRecorder.new { get :show, params: environment_params }
|
|
||||||
|
|
||||||
create_deployment_with_associations(sequence: 1)
|
|
||||||
create_deployment_with_associations(sequence: 2)
|
|
||||||
|
|
||||||
expect { get :show, params: environment_params }.not_to exceed_query_limit(control.count).with_threshold(34)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.shared_examples 'avoids N+1 queries on environment detail page' do
|
||||||
|
it 'avoids N+1 queries', :use_sql_query_cache do
|
||||||
|
create_deployment_with_associations(commit_depth: 19)
|
||||||
|
|
||||||
|
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
|
||||||
|
get project_environment_path(project, environment), params: environment_params
|
||||||
|
end
|
||||||
|
|
||||||
|
18.downto(0).each { |n| create_deployment_with_associations(commit_depth: n) }
|
||||||
|
|
||||||
|
# N+1s exist for loading commit emails and users
|
||||||
|
expect do
|
||||||
|
get project_environment_path(project, environment), params: environment_params
|
||||||
|
end.not_to exceed_all_query_limit(control).with_threshold(9)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue