Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-06-08 03:08:19 +00:00
parent 356e3c444d
commit bf293d4793
61 changed files with 614 additions and 299 deletions

View File

@ -542,7 +542,6 @@ That's all of the required database changes.
belongs_to :cool_widget, inverse_of: :cool_widget_state
validates :verification_failure, length: { maximum: 255 }
validates :verification_state, :cool_widget, presence: true
end
end

View File

@ -509,7 +509,6 @@ That's all of the required database changes.
belongs_to :cool_widget, inverse_of: :cool_widget_state
validates :verification_failure, length: { maximum: 255 }
validates :verification_state, :cool_widget, presence: true
end
end

View File

@ -133,15 +133,15 @@ export const METRIC_TOOLTIPS = {
},
[VULNERABILITY_METRICS.CRITICAL]: {
description: s__('ValueStreamAnalytics|Critical vulnerabilities over time.'),
groupLink: '-/security/vulnerabilities',
projectLink: '-/security/vulnerability_report',
docsLink: helpPagePath('user/application_security/vulnerability_report/index'),
groupLink: '-/security/vulnerabilities?severity=CRITICAL',
projectLink: '-/security/vulnerability_report?severity=CRITICAL',
docsLink: helpPagePath('user/application_security/vulnerabilities/severities.html'),
},
[VULNERABILITY_METRICS.HIGH]: {
description: s__('ValueStreamAnalytics|High vulnerabilities over time.'),
groupLink: '-/security/vulnerabilities',
projectLink: '-/security/vulnerability_report',
docsLink: helpPagePath('user/application_security/vulnerability_report/index'),
groupLink: '-/security/vulnerabilities?severity=HIGH',
projectLink: '-/security/vulnerability_report?severity=HIGH',
docsLink: helpPagePath('user/application_security/vulnerabilities/severities.html'),
},
[MERGE_REQUEST_METRICS.THROUGHPUT]: {
description: s__('ValueStreamAnalytics|The number of merge requests merged by month.'),

View File

@ -90,22 +90,6 @@ export default {
ALERT_COLLAPSED_FILES,
},
props: {
endpoint: {
type: String,
required: true,
},
endpointMetadata: {
type: String,
required: true,
},
endpointBatch: {
type: String,
required: true,
},
endpointDiffForPath: {
type: String,
required: true,
},
endpointCoverage: {
type: String,
required: false,
@ -116,15 +100,6 @@ export default {
required: false,
default: '',
},
endpointUpdateUser: {
type: String,
required: false,
default: '',
},
projectPath: {
type: String,
required: true,
},
shouldShow: {
type: Boolean,
required: false,
@ -144,51 +119,6 @@ export default {
required: false,
default: '',
},
isFluidLayout: {
type: Boolean,
required: false,
default: false,
},
dismissEndpoint: {
type: String,
required: false,
default: '',
},
showSuggestPopover: {
type: Boolean,
required: false,
default: false,
},
fileByFileUserPreference: {
type: Boolean,
required: false,
default: false,
},
defaultSuggestionCommitMessage: {
type: String,
required: false,
default: '',
},
rehydratedMrReviews: {
type: Object,
required: false,
default: () => ({}),
},
sourceProjectDefaultUrl: {
type: String,
required: false,
default: '',
},
sourceProjectFullPath: {
type: String,
required: false,
default: '',
},
isForked: {
type: Boolean,
required: false,
default: false,
},
},
data() {
const treeWidth =
@ -343,21 +273,6 @@ export default {
renderFileTree: 'adjustView',
},
mounted() {
this.setBaseConfig({
endpoint: this.endpoint,
endpointMetadata: this.endpointMetadata,
endpointBatch: this.endpointBatch,
endpointDiffForPath: this.endpointDiffForPath,
endpointCoverage: this.endpointCoverage,
endpointUpdateUser: this.endpointUpdateUser,
projectPath: this.projectPath,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
viewDiffsFileByFile: this.fileByFileUserPreference || false,
defaultSuggestionCommitMessage: this.defaultSuggestionCommitMessage,
mrReviews: this.rehydratedMrReviews,
});
if (this.endpointCodequality) {
this.setCodequalityEndpoint(this.endpointCodequality);
}

View File

@ -9,8 +9,6 @@ import eventHub from '../notes/event_hub';
import DiffsApp from './components/app.vue';
import { TREE_LIST_STORAGE_KEY, DIFF_WHITESPACE_COOKIE_NAME } from './constants';
import { getReviewsForMergeRequest } from './utils/file_reviews';
import { getDerivedMergeRequestInformation } from './utils/merge_request';
export default function initDiffsApp(store = notesStore) {
const el = document.getElementById('js-diffs-app');
@ -32,26 +30,13 @@ export default function initDiffsApp(store = notesStore) {
},
data() {
return {
endpoint: dataset.endpoint,
endpointMetadata: dataset.endpointMetadata || '',
endpointBatch: dataset.endpointBatch || '',
endpointDiffForPath: dataset.endpointDiffForPath || '',
endpointCoverage: dataset.endpointCoverage || '',
endpointCodequality: dataset.endpointCodequality || '',
endpointUpdateUser: dataset.updateCurrentUserPath,
projectPath: dataset.projectPath,
helpPagePath: dataset.helpPagePath,
currentUser: JSON.parse(dataset.currentUserData) || {},
changesEmptyStateIllustration: dataset.changesEmptyStateIllustration,
isFluidLayout: parseBoolean(dataset.isFluidLayout),
dismissEndpoint: dataset.dismissEndpoint,
showSuggestPopover: parseBoolean(dataset.showSuggestPopover),
showWhitespaceDefault: parseBoolean(dataset.showWhitespaceDefault),
viewDiffsFileByFile: parseBoolean(dataset.fileByFileDefault),
defaultSuggestionCommitMessage: dataset.defaultSuggestionCommitMessage,
sourceProjectDefaultUrl: dataset.sourceProjectDefaultUrl,
sourceProjectFullPath: dataset.sourceProjectFullPath,
isForked: parseBoolean(dataset.isForked),
};
},
computed: {
@ -90,31 +75,14 @@ export default function initDiffsApp(store = notesStore) {
...mapActions('diffs', ['setRenderTreeList', 'setShowWhitespace']),
},
render(createElement) {
const { mrPath } = getDerivedMergeRequestInformation({ endpoint: this.endpoint });
return createElement('diffs-app', {
props: {
endpoint: this.endpoint,
endpointMetadata: this.endpointMetadata,
endpointBatch: this.endpointBatch,
endpointDiffForPath: this.endpointDiffForPath,
endpointCoverage: this.endpointCoverage,
endpointCodequality: this.endpointCodequality,
endpointUpdateUser: this.endpointUpdateUser,
currentUser: this.currentUser,
projectPath: this.projectPath,
helpPagePath: this.helpPagePath,
shouldShow: this.activeTab === 'diffs',
changesEmptyStateIllustration: this.changesEmptyStateIllustration,
isFluidLayout: this.isFluidLayout,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
fileByFileUserPreference: this.viewDiffsFileByFile,
defaultSuggestionCommitMessage: this.defaultSuggestionCommitMessage,
rehydratedMrReviews: getReviewsForMergeRequest(mrPath),
sourceProjectDefaultUrl: this.sourceProjectDefaultUrl,
sourceProjectFullPath: this.sourceProjectFullPath,
isForked: this.isForked,
},
});
},

View File

@ -27,7 +27,7 @@ export const DRAWER_Z_INDEX = 252;
export const MIN_USERNAME_LENGTH = 2;
export const BYTES_FORMAT_BYTES = 'Bytes';
export const BYTES_FORMAT_BYTES = 'B';
export const BYTES_FORMAT_KIB = 'KiB';
export const BYTES_FORMAT_MIB = 'MiB';
export const BYTES_FORMAT_GIB = 'GiB';

View File

@ -106,7 +106,7 @@ export function numberToHumanSize(size, digits = 2) {
switch (format) {
case BYTES_FORMAT_BYTES:
return sprintf(__('%{size} bytes'), { size: humanSize });
return sprintf(__('%{size} B'), { size: humanSize });
case BYTES_FORMAT_KIB:
return sprintf(__('%{size} KiB'), { size: humanSize });
case BYTES_FORMAT_MIB:

View File

@ -1,12 +1,14 @@
import { parseBoolean } from '~/lib/utils/common_utils';
import store from '~/mr_notes/stores';
import mrNotes from '~/mr_notes/stores';
import { getLocationHash } from '~/lib/utils/url_utility';
import eventHub from '~/notes/event_hub';
import { initReviewBar } from '~/batch_comments';
import { initDiscussionCounter } from '~/mr_notes/discussion_counter';
import { initOverviewTabCounter } from '~/mr_notes/init_count';
import { getDerivedMergeRequestInformation } from '~/diffs/utils/merge_request';
import { getReviewsForMergeRequest } from '~/diffs/utils/file_reviews';
function setupMrNotesState(notesDataset) {
function setupMrNotesState(store, notesDataset, diffsDataset) {
const noteableData = JSON.parse(notesDataset.noteableData);
noteableData.noteableType = notesDataset.noteableType;
noteableData.targetType = notesDataset.targetType;
@ -15,26 +17,43 @@ function setupMrNotesState(notesDataset) {
const currentUserData = JSON.parse(notesDataset.currentUserData);
const endpoints = { metadata: notesDataset.endpointMetadata };
const { mrPath } = getDerivedMergeRequestInformation({ endpoint: diffsDataset.endpoint });
store.dispatch('setNotesData', notesData);
store.dispatch('setNoteableData', noteableData);
store.dispatch('setUserData', currentUserData);
store.dispatch('setTargetNoteHash', getLocationHash());
store.dispatch('setEndpoints', endpoints);
store.dispatch('diffs/setBaseConfig', {
endpoint: diffsDataset.endpoint,
endpointMetadata: diffsDataset.endpointMetadata,
endpointBatch: diffsDataset.endpointBatch,
endpointDiffForPath: diffsDataset.endpointDiffForPath,
endpointCoverage: diffsDataset.endpointCoverage,
endpointUpdateUser: diffsDataset.updateCurrentUserPath,
projectPath: diffsDataset.projectPath,
dismissEndpoint: diffsDataset.dismissEndpoint,
showSuggestPopover: parseBoolean(diffsDataset.showSuggestPopover),
viewDiffsFileByFile: parseBoolean(diffsDataset.fileByFileDefault),
defaultSuggestionCommitMessage: diffsDataset.defaultSuggestionCommitMessage,
mrReviews: getReviewsForMergeRequest(mrPath),
});
}
export function initMrStateLazyLoad({ reviewBarParams } = {}) {
export function initMrStateLazyLoad(store = mrNotes, { reviewBarParams } = {}) {
store.dispatch('setActiveTab', window.mrTabs.getCurrentAction());
window.mrTabs.eventHub.$on('MergeRequestTabChange', (value) =>
store.dispatch('setActiveTab', value),
);
const discussionsEl = document.getElementById('js-vue-mr-discussions');
const notesDataset = discussionsEl.dataset;
const diffsEl = document.getElementById('js-diffs-app');
let stop = () => {};
stop = store.watch(
(state) => state.page.activeTab,
(activeTab) => {
setupMrNotesState(notesDataset);
setupMrNotesState(store, discussionsEl.dataset, diffsEl.dataset);
// prevent loading MR state on commits and pipelines pages
// this is due to them having a shared controller with the Overview page

View File

@ -13,7 +13,7 @@ export default function initMrNotes(lazyLoadParams) {
action: mrShowNode.dataset.mrAction,
});
initMrStateLazyLoad(lazyLoadParams);
initMrStateLazyLoad(undefined, lazyLoadParams);
document.addEventListener('merged:UpdateActions', () => {
initRevertCommitModal('i_code_review_post_merge_submit_revert_modal');

View File

@ -68,7 +68,7 @@ export const MISSING_MANIFEST_WARNING_TOOLTIP = s__(
export const CREATED_AT = s__('ContainerRegistry|Created %{time}');
export const NOT_AVAILABLE_TEXT = __('Not applicable.');
export const NOT_AVAILABLE_SIZE = __('0 bytes');
export const NOT_AVAILABLE_SIZE = __('0 B');
export const CLEANUP_UNSCHEDULED_TEXT = s__('ContainerRegistry|Cleanup will run %{time}');
export const CLEANUP_SCHEDULED_TEXT = s__('ContainerRegistry|Cleanup pending');

View File

@ -16,7 +16,7 @@ export const DIGEST_LABEL = s__('HarborRegistry|Digest: %{imageId}');
export const CREATED_AT_LABEL = s__('HarborRegistry|Published %{timeInfo}');
export const NOT_AVAILABLE_TEXT = __('Not applicable.');
export const NOT_AVAILABLE_SIZE = __('0 bytes');
export const NOT_AVAILABLE_SIZE = __('0 B');
export const TOKEN_TYPE_TAG_NAME = 'tag_name';

View File

@ -216,7 +216,11 @@ export default {
>
<template #toggle>
<gl-button category="tertiary" icon="question-o" class="btn-with-notification">
<span v-if="showWhatsNewNotification" class="notification-dot-info"></span>
<span
v-if="showWhatsNewNotification"
data-testid="notification-dot"
class="notification-dot-info"
></span>
{{ $options.i18n.help }}
</gl-button>
</template>

View File

@ -489,14 +489,12 @@
padding: 0;
.issuable-context-form {
$issue-sticky-header-height: 76px;
top: calc(#{$calc-application-header-height} + #{$issue-sticky-header-height});
height: calc(#{$calc-application-viewport-height} - #{$issue-sticky-header-height} - var(--mr-review-bar-height) - $content-wrapper-padding);
top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height});
height: calc(#{$calc-application-viewport-height} - #{$mr-sticky-header-height} - var(--mr-review-bar-height));
position: sticky;
overflow: auto;
padding: 0 15px;
margin-bottom: calc((#{$header-height} + $issue-sticky-header-height) * -1);
margin-bottom: calc((#{$content-wrapper-padding} * -1) + var(--mr-review-bar-height));
}
}
}

View File

@ -44,6 +44,7 @@ class PersonalAccessToken < ApplicationRecord
validates :scopes, presence: true
validate :validate_scopes
validates :expires_at, presence: true, on: :create
validate :expires_at_before_instance_max_expiry_date, on: :create
def revoke!
@ -54,14 +55,6 @@ class PersonalAccessToken < ApplicationRecord
!revoked? && !expired?
end
# fall back to default value until background migration has updated all
# existing PATs and we can add a validation
# https://gitlab.com/gitlab-org/gitlab/-/issues/369123
def expires_at=(value)
datetime = value.presence || MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
super(datetime)
end
override :simple_sorts
def self.simple_sorts
super.merge(

View File

@ -13,7 +13,7 @@ module PersonalAccessTokens
def execute
return ServiceResponse.error(message: 'Not permitted to create') unless creation_permitted?
token = target_user.personal_access_tokens.create(params.slice(*allowed_params))
token = target_user.personal_access_tokens.create(personal_access_token_params)
if token.persisted?
log_event(token)
@ -31,13 +31,17 @@ module PersonalAccessTokens
attr_reader :target_user, :ip_address
def allowed_params
[
:name,
:impersonation,
:scopes,
:expires_at
]
def personal_access_token_params
{
name: params[:name],
impersonation: params[:impersonation] || false,
scopes: params[:scopes],
expires_at: pat_expiration
}
end
def pat_expiration
params[:expires_at].presence || PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
end
def creation_permitted?

View File

@ -97,7 +97,7 @@ module ResourceAccessTokens
name: params[:name] || "#{resource_type}_bot",
impersonation: false,
scopes: params[:scopes] || default_scopes,
expires_at: params[:expires_at] || nil
expires_at: pat_expiration
}
end
@ -106,10 +106,10 @@ module ResourceAccessTokens
end
def create_membership(resource, user, access_level)
resource.add_member(user, access_level, expires_at: default_pat_expiration)
resource.add_member(user, access_level, expires_at: pat_expiration)
end
def default_pat_expiration
def pat_expiration
params[:expires_at].presence || PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
end

View File

@ -0,0 +1,8 @@
---
name: adherence_report_ui
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122374
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414495
milestone: '16.1'
type: development
group: group::compliance
default_enabled: false

View File

@ -179,6 +179,7 @@ options:
- p_ci_templates_katalon
- p_ci_templates_terraform_module_base
- p_ci_templates_terraform_module
- p_ci_templates_pages_zola
distribution:
- ce
- ee

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_pages_zola_monthly
description: Count of pipelines using the Zola Pages template
product_section: ''
product_stage: ''
product_group: ''
value_type: number
status: active
milestone: "16.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121946
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_pages_zola

View File

@ -180,6 +180,7 @@ options:
- p_ci_templates_katalon
- p_ci_templates_terraform_module_base
- p_ci_templates_terraform_module
- p_ci_templates_pages_zola
distribution:
- ce
- ee

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_pages_zola_weekly
description: Count of pipelines using the Zola Pages template
product_section: ''
product_stage: ''
product_group: ''
value_type: number
status: active
milestone: "16.1"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121946
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_pages_zola

View File

@ -12,6 +12,7 @@ Gitlab::Seeder.quiet do
}
user.personal_access_tokens.build(params).tap do |pat|
pat.expires_at = 365.days.from_now
pat.set_token(token)
pat.save!
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddNotNullConstraintToPersonalAccessTokensExpiresAt < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
def up
add_not_null_constraint :personal_access_tokens, :expires_at, validate: false
end
def down
remove_not_null_constraint :personal_access_tokens, :expires_at
end
end

View File

@ -0,0 +1 @@
d630b2bbfbb4ac030da8020745005bf7326b337ea9dbf4a57130e95d1824b780

View File

@ -26899,6 +26899,9 @@ ALTER TABLE users
ALTER TABLE vulnerability_scanners
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
ALTER TABLE personal_access_tokens
ADD CONSTRAINT check_b8d60815eb CHECK ((expires_at IS NOT NULL)) NOT VALID;
ALTER TABLE sprints
ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID;

View File

@ -23733,7 +23733,7 @@ Represents a state transition of a vulnerability.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="vulnerabilitystatetransitiontypeauthor"></a>`author` | [`UserCore!`](#usercore) | User who changed the state of the vulnerability. |
| <a id="vulnerabilitystatetransitiontypeauthor"></a>`author` | [`UserCore`](#usercore) | User who changed the state of the vulnerability. |
| <a id="vulnerabilitystatetransitiontypecomment"></a>`comment` | [`String`](#string) | Comment for the state change. |
| <a id="vulnerabilitystatetransitiontypecreatedat"></a>`createdAt` | [`Time!`](#time) | Time of the state change of the vulnerability. |
| <a id="vulnerabilitystatetransitiontypedismissalreason"></a>`dismissalReason` | [`VulnerabilityDismissalReason`](#vulnerabilitydismissalreason) | Reason for the dismissal. |

View File

@ -342,7 +342,7 @@ In the example above:
job log, but they are displayed in the raw job log. To see them, in the upper-right corner
of the job log, select **Show complete raw** (**{doc-text}**).
- `\r`: carriage return.
- `\e[0K`: clear line ANSI escape code.
- `\e[0K`: clear line ANSI escape sequence (`\e[K` does not work, the `0` must be included).
Sample raw job log:

View File

@ -29,7 +29,7 @@ Here is an example on how to use database helpers to create a new table and fore
end
add_concurrent_partitioned_foreign_key(
:p_ci_examples, :ci_builds,
:p_ci_examples, :p_ci_builds,
column: [:partition_id, :build_id],
target_column: [:partition_id, :id],
on_update: :cascade,
@ -51,7 +51,7 @@ When creating the routing table:
- The table name must start with the `p_` prefix. There are analyzers in place to ensure that all queries go
through the routing tables and do not access the partitions directly.
- Each new table needs a `partition_id` column and its value must equal
the value from the related association. In this example, that is `ci_builds`. All resources
the value from the related association. In this example, that is `p_ci_builds`. All resources
belonging to a pipeline share the same `partition_id` value.
- The primary key must have the columns ordered this way to allow efficient
search only by `id`.
@ -74,7 +74,7 @@ the application runs:
def up
with_lock_retries do
connection.execute(<<~SQL)
LOCK TABLE ci_builds IN SHARE UPDATE EXCLUSIVE MODE;
LOCK TABLE p_ci_builds IN SHARE ROW EXCLUSIVE MODE;
LOCK TABLE ONLY p_ci_examples IN ACCESS EXCLUSIVE MODE;
SQL
@ -92,7 +92,7 @@ Partitions are created in `gitlab_partitions_dynamic` schema.
When creating a partition, remember:
- Partition names do not use the `p_` prefix.
- The default value for `partition_id` is `100`.
- The starting value for `partition_id` is `100`.
## Cascade the partition value

View File

@ -856,7 +856,7 @@ Sometimes they are more precise and will be maintained more actively.
For each external link you add, weigh the customer benefit with the maintenance difficulties.
### Links requiring permissions
### Links that require permissions
Don't link directly to:
@ -864,23 +864,26 @@ Don't link directly to:
- Project features that require [special permissions](../../../user/permissions.md)
to view.
These fail for:
These links fail for:
- Those without sufficient permissions.
- Automated link checkers.
Instead:
If you must use one of these links:
- To reduce confusion, mention in the text that the information is either:
- Contained in a confidential issue.
- Requires special permission to a project to view.
- Provide a link in back ticks (`` ` ``) so that those with access to the issue
can navigate to it.
- Mention that the information is confidential or requires specific permissions.
- Put the link in backticks, so that it does not cause link checkers to fail.
Example:
Examples:
```markdown
For more information, see the [confidential issue](../../../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab-foss/-/issues/<issue_number>`.
GitLab team members can view more information in this confidential issue:
`https://gitlab.com/gitlab-org/gitlab/-/issues/<issue_number>`
```
```markdown
Users with the Maintainer role for the project can use the pipeline editor:
`https://gitlab.com/gitlab-org/gitlab/-/ci/editor`
```
### Link to specific lines of code

View File

@ -74,7 +74,7 @@ To purge files from a GitLab repository:
1. Clone a fresh copy of the repository from the bundle using `--bare` and `--mirror` options:
```shell
git clone --bare /path/to/project.bundle
git clone --bare --mirror /path/to/project.bundle
```
1. Go to the `project.git` directory:
@ -134,6 +134,12 @@ To purge files from a GitLab repository:
Repeat this step and all following steps (including the [repository cleanup](#repository-cleanup) step)
every time you run any `git filter-repo` command.
1. To allow you to force push the changes you need to unset the mirror flag:
```shell
git config --unset remote.origin.mirror
```
1. Force push your changes to overwrite all branches on GitLab:
```shell
@ -167,8 +173,6 @@ To purge files from a GitLab repository:
## Repository cleanup
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/19376) in GitLab 11.6.
Repository cleanup allows you to upload a text file of objects and GitLab removes internal Git
references to these objects. You can use
[`git filter-repo`](https://github.com/newren/git-filter-repo) to produce a list of objects (in a
@ -180,6 +184,10 @@ of the operation. This happens automatically, but submitting the cleanup request
fails if any writes are ongoing, so cancel any outstanding `git push`
operations before continuing.
WARNING:
Removing internal Git references results in associated merge request commits, pipelines, and changes details
no longer being available.
To clean up a repository:
1. Go to the project for the repository.

View File

@ -92,17 +92,30 @@ module API
success Entities::ResourceAccessTokenWithToken
end
params do
requires :id, type: String, desc: "The #{source_type} ID", documentation: { example: 2 }
requires :name, type: String, desc: "Resource access token name", documentation: { example: 'test' }
requires :scopes, type: Array[String], values: ::Gitlab::Auth.resource_bot_scopes.map(&:to_s),
desc: "The permissions of the token",
documentation: { example: %w[api read_repository] }
optional :access_level, type: Integer,
values: ALLOWED_RESOURCE_ACCESS_LEVELS.values,
default: Gitlab::Access::MAINTAINER,
desc: "The access level of the token in the #{source_type}",
documentation: { example: 40 }
optional :expires_at, type: Date, desc: "The expiration date of the token", documentation: { example: '"2021-01-31' }
requires :id,
type: String,
desc: "The #{source_type} ID",
documentation: { example: 2 }
requires :name,
type: String,
desc: "Resource access token name",
documentation: { example: 'test' }
requires :scopes,
type: Array[String],
values: ::Gitlab::Auth.resource_bot_scopes.map(&:to_s),
desc: "The permissions of the token",
documentation: { example: %w[api read_repository] }
requires :expires_at,
type: Date,
desc: "The expiration date of the token",
default: PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now,
documentation: { example: '"2021-01-31' }
optional :access_level,
type: Integer,
values: ALLOWED_RESOURCE_ACCESS_LEVELS.values,
default: Gitlab::Access::MAINTAINER,
desc: "The access level of the token in the #{source_type}",
documentation: { example: 40 }
end
post ':id/access_tokens' do
resource = find_source(source_type, params[:id])

View File

@ -8,9 +8,9 @@
code_quality:
stage: test
image: "cirrusci/flutter:1.22.5"
image: "ghcr.io/cirruslabs/flutter:3.10.3"
before_script:
- pub global activate dart_code_metrics
- flutter pub global activate dart_code_metrics
- export PATH="$PATH:$HOME/.pub-cache/bin"
script:
- metrics lib -r codeclimate > gl-code-quality-report.json
@ -20,9 +20,9 @@ code_quality:
test:
stage: test
image: "cirrusci/flutter:1.22.5"
image: "ghcr.io/cirruslabs/flutter:3.10.3"
before_script:
- pub global activate junitreport
- flutter pub global activate junitreport
- export PATH="$PATH:$HOME/.pub-cache/bin"
script:
- flutter test --machine --coverage | tojunit -o report.xml

View File

@ -1,5 +1,5 @@
variables:
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.49.0'
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.50.0'
.dast-auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.49.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.50.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.49.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.50.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -0,0 +1,30 @@
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Pages/Zola.gitlab-ci.yml
# Prefer to copy-paste this template instead of include it to ensure forward compatibility
---
# From: https://www.getzola.org/documentation/deployment/gitlab-pages/
# Source template is slightly modified to be self-contained
pages:
image: alpine:latest
variables:
# This variable will ensure that the CI runner pulls in your theme from the submodule
GIT_SUBMODULE_STRATEGY: recursive
before_script:
# Install the zola package from the alpine community repositories
- apk add --update-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/community/ zola
script:
# Execute zola build
- zola build --base-url "$CI_PAGES_URL"
artifacts:
paths:
# Path of our artifacts
- public
# This config will only publish changes that are pushed on the default branch
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
environment: production

View File

@ -123,8 +123,10 @@ module Gitlab
end
def tree_entries(repository, revision, path, recursive, skip_flat_paths, pagination_params)
pagination_params ||= {}
pagination_params[:limit] ||= TREE_ENTRIES_DEFAULT_LIMIT
unless pagination_params.nil? && recursive
pagination_params ||= {}
pagination_params[:limit] ||= TREE_ENTRIES_DEFAULT_LIMIT
end
request = Gitaly::GetTreeEntriesRequest.new(
repository: @gitaly_repo,

View File

@ -307,3 +307,5 @@
aggregation: weekly
- name: p_ci_templates_terraform_module
aggregation: weekly
- name: p_ci_templates_pages_zola
aggregation: weekly

View File

@ -1084,6 +1084,9 @@ msgid_plural "%{selectedProjectsCount} projects"
msgstr[0] ""
msgstr[1] ""
msgid "%{size} B"
msgstr ""
msgid "%{size} GiB"
msgstr ""
@ -1093,9 +1096,6 @@ msgstr ""
msgid "%{size} MiB"
msgstr ""
msgid "%{size} bytes"
msgstr ""
msgid "%{sourceBranch} into %{targetBranch}"
msgstr ""
@ -1538,7 +1538,7 @@ msgstr ""
msgid "/day"
msgstr ""
msgid "0 bytes"
msgid "0 B"
msgstr ""
msgid "1 Code quality finding"
@ -11483,6 +11483,9 @@ msgstr ""
msgid "Compliance Report|Frameworks"
msgstr ""
msgid "Compliance Report|Standards Adherence"
msgstr ""
msgid "Compliance Report|Violations"
msgstr ""

View File

@ -0,0 +1,21 @@
image: node:latest
stages:
- install
install:
stage: install
script:
- "npm config set @<%= registry_scope %>:registry <%= gitlab_address_with_port %>/api/v4/groups/<%= another_project.group.id %>/-/packages/npm/"
- "npm install <%= package.name %>"
cache:
key: ${CI_COMMIT_REF_NAME}
paths:
- node_modules/
artifacts:
paths:
- node_modules/
only:
- "<%= another_project.default_branch %>"
tags:
- "runner-for-<%= another_project.group.name %>"

View File

@ -0,0 +1,14 @@
image: node:latest
stages:
- deploy
deploy:
stage: deploy
script:
- echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=<%= auth_token %>">.npmrc
- npm publish
only:
- "<%= project.default_branch %>"
tags:
- "runner-for-<%= project.group.name %>"

View File

@ -1,8 +0,0 @@
{
"name": "<%= package.name %>",
"version": "1.0.0",
"description": "Example package for GitLab npm registry",
"publishConfig": {
"@<%= registry_scope %>:registry": "<%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/npm/"
}
}

View File

@ -0,0 +1,175 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Package' do
describe 'Package Registry', :skip_live_env, :orchestrated, :packages, :object_storage,
product_group: :package_registry do
describe 'npm group level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
include Support::Helpers::MaskToken
let!(:registry_scope) { Runtime::Namespace.sandbox_name }
let!(:personal_access_token) do
Flow::Login.sign_in unless Page::Main::Menu.perform(&:signed_in?)
Resource::PersonalAccessToken.fabricate!.token
end
let(:project_deploy_token) do
Resource::ProjectDeployToken.fabricate_via_api! do |deploy_token|
deploy_token.name = 'npm-deploy-token'
deploy_token.project = project
deploy_token.scopes = %w[
read_repository
read_package_registry
write_package_registry
]
end
end
let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) }
let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" }
let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" }
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'npm-group-level-publish'
end
end
let!(:another_project) do
Resource::Project.fabricate_via_api! do |another_project|
another_project.name = 'npm-group-level-install'
another_project.group = project.group
end
end
let!(:runner) do
Resource::GroupRunner.fabricate! do |runner|
runner.name = "qa-runner-#{Time.now.to_i}"
runner.tags = ["runner-for-#{project.group.name}"]
runner.executor = :docker
runner.group = project.group
end
end
let(:package) do
Resource::Package.init do |package|
package.name = "@#{registry_scope}/#{project.name}-#{SecureRandom.hex(8)}"
package.project = project
end
end
after do
package.remove_via_api!
runner.remove_via_api!
project.remove_via_api!
another_project.remove_via_api!
end
where(:case_name, :authentication_token_type, :token_name, :testcase) do
'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/413760'
'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/413761'
'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/413762'
end
with_them do
let(:auth_token) do
case authentication_token_type
when :personal_access_token
use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project)
use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: another_project)
when :ci_job_token
'${CI_JOB_TOKEN}'
when :project_deploy_token
use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project)
use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: another_project)
end
end
it 'push and pull a npm package via CI', testcase: params[:testcase] do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
npm_upload_yaml = ERB.new(read_fixture('package_managers/npm',
'npm_upload_package_group.yaml.erb')).result(binding)
package_json = ERB.new(read_fixture('package_managers/npm', 'package.json.erb')).result(binding)
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.commit_message = 'Add files'
commit.add_files([
{
file_path: '.gitlab-ci.yml',
content: npm_upload_yaml
},
{
file_path: 'package.json',
content: package_json
}
])
end
end
project.visit!
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('deploy')
end
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful(timeout: 800)
end
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
npm_install_yaml = ERB.new(read_fixture('package_managers/npm',
'npm_install_package_group.yaml.erb')).result(binding)
commit.project = another_project
commit.commit_message = 'Add .gitlab-ci.yml'
commit.add_files([
{
file_path: '.gitlab-ci.yml',
content: npm_install_yaml
}
])
end
end
another_project.visit!
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('install')
end
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful(timeout: 800)
job.click_browse_button
end
Page::Project::Artifact::Show.perform do |artifacts|
artifacts.go_to_directory('node_modules')
artifacts.go_to_directory("@#{registry_scope}")
expect(artifacts).to have_content(project.name.to_s)
end
project.visit!
Page::Project::Menu.perform(&:go_to_package_registry)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package.name)
index.click_package(package.name)
end
Page::Project::Packages::Show.perform do |show|
expect(show).to have_package_info(package.name, "1.0.0")
end
end
end
end
end
end
end

View File

@ -90,7 +90,7 @@ module QA
it 'push and pull a npm package via CI', testcase: params[:testcase] do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_package_instance.yaml.erb')).result(binding)
package_json = ERB.new(read_fixture('package_managers/npm', 'package_instance.json.erb')).result(binding)
package_json = ERB.new(read_fixture('package_managers/npm', 'package.json.erb')).result(binding)
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project

View File

@ -81,7 +81,7 @@ module QA
it 'push and pull a npm package via CI', testcase: params[:testcase] do
Resource::Repository::Commit.fabricate_via_api! do |commit|
npm_upload_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_install_package_project.yaml.erb')).result(binding)
package_json = ERB.new(read_fixture('package_managers/npm', 'package_project.json.erb')).result(binding)
package_json = ERB.new(read_fixture('package_managers/npm', 'package.json.erb')).result(binding)
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'

View File

@ -5,7 +5,7 @@ FactoryBot.define do
user
sequence(:name) { |n| "PAT #{n}" }
revoked { false }
expires_at { 5.days.from_now }
expires_at { 30.days.from_now }
scopes { ['api'] }
impersonation { false }

View File

@ -43,7 +43,7 @@ describe('diffs/components/app', () => {
let wrapper;
let mock;
function createComponent(props = {}, extendStore = () => {}, provisions = {}) {
function createComponent(props = {}, extendStore = () => {}, provisions = {}, baseConfig = {}) {
const provide = {
...provisions,
glFeatures: {
@ -57,20 +57,24 @@ describe('diffs/components/app', () => {
extendStore(store);
store.dispatch('diffs/setBaseConfig', {
endpoint: TEST_ENDPOINT,
endpointMetadata: `${TEST_HOST}/diff/endpointMetadata`,
endpointBatch: `${TEST_HOST}/diff/endpointBatch`,
endpointDiffForPath: TEST_ENDPOINT,
projectPath: 'namespace/project',
dismissEndpoint: '',
showSuggestPopover: true,
mrReviews: {},
...baseConfig,
});
wrapper = shallowMount(App, {
propsData: {
endpoint: TEST_ENDPOINT,
endpointMetadata: `${TEST_HOST}/diff/endpointMetadata`,
endpointBatch: `${TEST_HOST}/diff/endpointBatch`,
endpointDiffForPath: TEST_ENDPOINT,
endpointCoverage: `${TEST_HOST}/diff/endpointCoverage`,
endpointCodequality: '',
projectPath: 'namespace/project',
currentUser: {},
changesEmptyStateIllustration: '',
dismissEndpoint: '',
showSuggestPopover: true,
fileByFileUserPreference: false,
...props,
},
provide,
@ -653,13 +657,18 @@ describe('diffs/components/app', () => {
describe('file-by-file', () => {
it('renders a single diff', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123' },
312: { type: 'blob', fileHash: '312' },
};
state.diffs.diffFiles.push({ file_hash: '312' });
});
createComponent(
undefined,
({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123' },
312: { type: 'blob', fileHash: '312' },
};
state.diffs.diffFiles.push({ file_hash: '312' });
},
undefined,
{ viewDiffsFileByFile: true },
);
await nextTick();
@ -671,12 +680,17 @@ describe('diffs/components/app', () => {
const paginator = () => fileByFileNav().findComponent(GlPagination);
it('sets previous button as disabled', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123' },
312: { type: 'blob', fileHash: '312' },
};
});
createComponent(
undefined,
({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123' },
312: { type: 'blob', fileHash: '312' },
};
},
undefined,
{ viewDiffsFileByFile: true },
);
await nextTick();
@ -685,13 +699,18 @@ describe('diffs/components/app', () => {
});
it('sets next button as disabled', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123' },
312: { type: 'blob', fileHash: '312' },
};
state.diffs.currentDiffFileId = '312';
});
createComponent(
undefined,
({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123' },
312: { type: 'blob', fileHash: '312' },
};
state.diffs.currentDiffFileId = '312';
},
undefined,
{ viewDiffsFileByFile: true },
);
await nextTick();
@ -700,10 +719,15 @@ describe('diffs/components/app', () => {
});
it("doesn't display when there's fewer than 2 files", async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.treeEntries = { 123: { type: 'blob', fileHash: '123' } };
state.diffs.currentDiffFileId = '123';
});
createComponent(
undefined,
({ state }) => {
state.diffs.treeEntries = { 123: { type: 'blob', fileHash: '123' } };
state.diffs.currentDiffFileId = '123';
},
undefined,
{ viewDiffsFileByFile: true },
);
await nextTick();
@ -717,13 +741,18 @@ describe('diffs/components/app', () => {
`(
'calls navigateToDiffFileIndex with $index when $link is clicked',
async ({ currentDiffFileId, targetFile }) => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123', filePaths: { old: '1234', new: '123' } },
312: { type: 'blob', fileHash: '312', filePaths: { old: '3124', new: '312' } },
};
state.diffs.currentDiffFileId = currentDiffFileId;
});
createComponent(
undefined,
({ state }) => {
state.diffs.treeEntries = {
123: { type: 'blob', fileHash: '123', filePaths: { old: '1234', new: '123' } },
312: { type: 'blob', fileHash: '312', filePaths: { old: '3124', new: '312' } },
};
state.diffs.currentDiffFileId = currentDiffFileId;
},
undefined,
{ viewDiffsFileByFile: true },
);
await nextTick();

View File

@ -109,8 +109,8 @@ describe('Number Utils', () => {
describe('numberToHumanSize', () => {
it('should return bytes', () => {
expect(numberToHumanSize(654)).toEqual('654 bytes');
expect(numberToHumanSize(-654)).toEqual('-654 bytes');
expect(numberToHumanSize(654)).toEqual('654 B');
expect(numberToHumanSize(-654)).toEqual('-654 B');
});
it('should return KiB', () => {

View File

@ -132,7 +132,7 @@ describe('Harbor artifact list row', () => {
},
});
expect(findByTestId('size').text()).toBe('0 bytes');
expect(findByTestId('size').text()).toBe('0 B');
});
});
});

View File

@ -51,7 +51,7 @@ describe('PackageTitle', () => {
it('correctly calculates the size', async () => {
await createComponent();
expect(packageSize().props('text')).toBe('300 bytes');
expect(packageSize().props('text')).toBe('300 B');
});
});

View File

@ -25,6 +25,7 @@ describe('HelpCenter component', () => {
};
const withinComponent = () => within(wrapper.element);
const findButton = (name) => withinComponent().getByRole('button', { name });
const findNotificationDot = () => wrapper.findByTestId('notification-dot');
// eslint-disable-next-line no-shadow
const createWrapper = (sidebarData) => {
@ -203,8 +204,8 @@ describe('HelpCenter component', () => {
createWrapper({ ...sidebarData, display_whats_new: false });
});
it('is false', () => {
expect(wrapper.vm.showWhatsNewNotification).toBe(false);
it('does not render notification dot', () => {
expect(findNotificationDot().exists()).toBe(false);
});
});
@ -215,8 +216,8 @@ describe('HelpCenter component', () => {
createWrapper({ ...sidebarData, display_whats_new: true });
});
it('is true', () => {
expect(wrapper.vm.showWhatsNewNotification).toBe(true);
it('renders notification dot', () => {
expect(findNotificationDot().exists()).toBe(true);
});
describe('when "What\'s new" drawer got opened', () => {
@ -224,8 +225,8 @@ describe('HelpCenter component', () => {
findButton("What's new 5").click();
});
it('is false', () => {
expect(wrapper.vm.showWhatsNewNotification).toBe(false);
it('does not render notification dot', () => {
expect(findNotificationDot().exists()).toBe(false);
});
});
@ -235,8 +236,8 @@ describe('HelpCenter component', () => {
createWrapper({ ...sidebarData, display_whats_new: true });
});
it('is false', () => {
expect(wrapper.vm.showWhatsNewNotification).toBe(false);
it('does not render notification dot', () => {
expect(findNotificationDot().exists()).toBe(false);
});
});
});

View File

@ -3,6 +3,7 @@ import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { stubPerformanceWebAPI } from 'helpers/performance';
import initDiffsApp from '~/diffs';
import { initMrStateLazyLoad } from '~/mr_notes/init';
import { createStore } from '~/mr_notes/stores';
import {
getDiffCodePart,
@ -53,23 +54,35 @@ const startDiffsApp = () => {
endpointBatch: `${TEST_BASE_URL}diffs_batch.json`,
projectPath: TEST_PROJECT_PATH,
helpPagePath: '/help',
currentUserData: 'null',
currentUserData: '{}',
changesEmptyStateIllustration: '',
isFluidLayout: 'false',
dismissEndpoint: '',
showSuggestPopover: 'false',
showWhitespaceDefault: 'true',
viewDiffsFileByFile: 'false',
fileByFileDefault: 'false',
defaultSuggestionCommitMessage: 'Lorem ipsum',
});
const notesEl = document.createElement('div');
notesEl.id = 'js-vue-mr-discussions';
document.body.appendChild(notesEl);
Object.assign(notesEl.dataset, {
noteableData: '{ "current_user": {} }',
notesData: '{}',
currentUserData: '{}',
});
window.mrTabs = {
getCurrentAction: () => 'diffs',
eventHub: {
$on() {},
},
};
const store = createStore();
initMrStateLazyLoad(store);
const vm = initDiffsApp(store);
store.dispatch('setActiveTab', 'diffs');
return vm;
return initDiffsApp(store);
};
describe('diffs third party interoperability', () => {
@ -117,7 +130,7 @@ describe('diffs third party interoperability', () => {
${'parallel view right side'} | ${'parallel'} | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content.right-side'} | ${EXPECT_PARALLEL_RIGHT_SIDE}
`('$desc', ({ view, rowSelector, codeSelector, expectation }) => {
beforeEach(async () => {
setWindowLocation(`${TEST_HOST}/${TEST_BASE_URL}/diffs?view=${view}`);
setWindowLocation(`${TEST_HOST}${TEST_BASE_URL}diffs?view=${view}`);
vm = startDiffsApp();

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe API::Entities::PersonalAccessToken do
describe '#as_json' do
let_it_be(:user) { create(:user) }
let_it_be(:token) { create(:personal_access_token, user: user, expires_at: nil) }
let_it_be(:token) { create(:personal_access_token, user: user) }
let(:entity) { described_class.new(token) }

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Pages/Zola.gitlab-ci.yml', feature_category: :pages do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Pages/Zola') }
describe 'the created pipeline' do
let_it_be(:project) { create(:project, :repository) }
let(:user) { project.first_owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: project.default_branch) }
let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) }
before do
stub_ci_pipeline_yaml_file(template.content)
allow(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
end
it 'creates "pages" job' do
expect(build_names).to include('pages')
end
end
end

View File

@ -208,6 +208,19 @@ RSpec.describe Gitlab::GitalyClient::CommitService, feature_category: :gitaly do
is_expected.to eq([[], nil])
end
context 'when recursive is "true"' do
let(:recursive) { true }
it 'sends a get_tree_entries message without the limit' do
expect_any_instance_of(Gitaly::CommitService::Stub)
.to receive(:get_tree_entries)
.with(gitaly_request_with_params({ pagination_params: nil }), kind_of(Hash))
.and_return([])
is_expected.to eq([[], nil])
end
end
context 'with UTF-8 params strings' do
let(:revision) { "branch\u011F" }
let(:path) { "foo/\u011F.txt" }

View File

@ -130,7 +130,7 @@ RSpec.describe PersonalAccessToken, 'TokenAuthenticatable' do
let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) }
let(:user) { create(:user) }
let(:personal_access_token) do
described_class.new(name: 'test-pat-01', user_id: user.id, scopes: [:api], token_digest: token_digest)
described_class.new(name: 'test-pat-01', user_id: user.id, scopes: [:api], token_digest: token_digest, expires_at: 30.days.from_now)
end
before do

View File

@ -259,6 +259,13 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
context 'validates expires_at' do
let(:max_expiration_date) { described_class::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now }
it "can't be blank" do
personal_access_token.expires_at = nil
expect(personal_access_token).not_to be_valid
expect(personal_access_token.errors[:expires_at].first).to eq("can't be blank")
end
context 'when expires_in is less than MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS days' do
it 'is valid' do
personal_access_token.expires_at = max_expiration_date - 1.day
@ -285,11 +292,10 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
let_it_be(:not_revoked_nil_token) { create(:personal_access_token, revoked: nil) }
let_it_be(:expired_token) { create(:personal_access_token, :expired) }
let_it_be(:not_expired_token) { create(:personal_access_token) }
let_it_be(:never_expires_token) { create(:personal_access_token, expires_at: nil) }
it 'includes non-revoked and non-expired tokens' do
it 'includes non-revoked tokens' do
expect(described_class.active)
.to match_array([not_revoked_false_token, not_revoked_nil_token, not_expired_token, never_expires_token])
.to match_array([not_revoked_false_token, not_revoked_nil_token, not_expired_token])
end
end
@ -414,22 +420,4 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
end
end
end
describe '#expires_at=' do
let(:personal_access_token) { described_class.new }
context 'expires_at set to empty value' do
[nil, ""].each do |expires_in_value|
it 'defaults to PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
personal_access_token.expires_at = expires_in_value
freeze_time do
expect(personal_access_token.expires_at).to eq(
PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now.to_date
)
end
end
end
end
end
end

View File

@ -225,7 +225,8 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
params: {
key_id: key.id,
name: 'newtoken',
scopes: %w(read_api read_repository)
scopes: %w(read_api read_repository),
expires_at: 365.days.from_now
},
headers: gitlab_shell_internal_api_request_header

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe AccessTokenEntityBase do
let_it_be(:user) { create(:user) }
let_it_be(:token) { create(:personal_access_token, user: user, expires_at: nil) }
let_it_be(:token) { create(:personal_access_token, user: user) }
subject(:json) { described_class.new(token).as_json }

View File

@ -67,6 +67,13 @@ RSpec.describe PersonalAccessTokens::CreateService, feature_category: :system_ac
end
end
context 'with no expires_at set', :freeze_time do
let(:params) { { name: 'Test token', impersonation: false, scopes: [:no_valid] } }
let(:service) { described_class.new(current_user: user, target_user: user, params: params) }
it { expect(subject.payload[:personal_access_token].expires_at).to eq PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now.to_date }
end
context 'when invalid scope' do
let(:params) { { name: 'Test token', impersonation: false, scopes: [:no_valid], expires_at: Date.today + 1.month } }