Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PROCESS.md222
-rw-r--r--app/assets/javascripts/access_tokens/components/access_token_table_app.vue17
-rw-r--r--app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue48
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js3
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue4
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js35
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue165
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue2
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue4
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js10
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql50
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/app.vue5
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue37
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js12
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/incidents.scss73
-rw-r--r--app/assets/stylesheets/page_bundles/prometheus.scss (renamed from app/assets/stylesheets/pages/prometheus.scss)2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss100
-rw-r--r--app/helpers/ci/pipeline_editor_helper.rb12
-rw-r--r--app/helpers/ci/pipelines_helper.rb3
-rw-r--r--app/models/project.rb9
-rw-r--r--app/models/user.rb1
-rw-r--r--app/models/users/phone_number_validation.rb41
-rw-r--r--app/models/wiki.rb10
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/uploaders/object_storage/cdn/google_cdn.rb2
-rw-r--r--app/views/clusters/clusters/_health.html.haml2
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/branch_rules/_show.html.haml2
-rw-r--r--app/views/projects/environments/metrics.html.haml2
-rw-r--r--app/views/projects/incidents/show.html.haml1
-rw-r--r--app/views/projects/issues/show.html.haml1
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml10
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/settings/branch_rules/index.html.haml4
-rw-r--r--config/application.rb2
-rw-r--r--config/feature_flags/development/realtime_labels.yml2
-rw-r--r--db/docs/user_phone_number_validations.yml9
-rw-r--r--db/migrate/20220913043728_create_user_phone_number_validations.rb29
-rw-r--r--db/migrate/20220927155407_add_column_inbound_job_token_scope_enabled_to_ci_cd_setting.rb13
-rw-r--r--db/schema_migrations/202209130437281
-rw-r--r--db/schema_migrations/202209271554071
-rw-r--r--db/structure.sql27
-rw-r--r--doc/administration/object_storage.md6
-rw-r--r--doc/development/contributing/community_roles.md4
-rw-r--r--doc/development/contributing/index.md5
-rw-r--r--doc/development/development_processes.md2
-rw-r--r--doc/development/migration_style_guide.md8
-rw-r--r--doc/policy/maintenance.md3
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/user/project/labels.md4
-rw-r--r--lib/banzai/pipeline/ascii_doc_pipeline.rb1
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml1
-rw-r--r--lib/gitlab/git/wiki.rb4
-rw-r--r--locale/gitlab.pot51
-rw-r--r--spec/factories/projects.rb2
-rw-r--r--spec/factories/users/phone_number_validations.rb10
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb33
-rw-r--r--spec/frontend/access_tokens/components/access_token_table_app_spec.js29
-rw-r--r--spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js30
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js21
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/index_spec.js84
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/mock_data.js77
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js4
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/app_spec.js14
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js30
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/mock_data.js13
-rw-r--r--spec/helpers/ci/pipeline_editor_helper_spec.rb96
-rw-r--r--spec/lib/object_storage/config_spec.rb1
-rw-r--r--spec/models/project_spec.rb6
-rw-r--r--spec/models/user_spec.rb1
-rw-r--r--spec/models/users/phone_number_validation_spec.rb81
-rw-r--r--spec/models/wiki_page_spec.rb148
-rw-r--r--spec/requests/api/project_attributes.yml3
-rw-r--r--spec/support/shared_examples/models/wiki_shared_examples.rb51
-rw-r--r--spec/uploaders/object_storage/cdn/google_cdn_spec.rb20
76 files changed, 1284 insertions, 543 deletions
diff --git a/PROCESS.md b/PROCESS.md
index 2757075e354..699a08bc9c8 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -1,221 +1,3 @@
-## GitLab core team & GitLab Inc. contribution process
+The content of this document was moved to [another location](https://docs.gitlab.com/ee/development/contributing/).
----
-
-<!-- START doctoc generated TOC please keep comment here to allow auto update -->
-<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
-**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
-
-- [Purpose of describing the contributing process](#purpose-of-describing-the-contributing-process)
-- [Common actions](#common-actions)
- - [Merge request coaching](#merge-request-coaching)
-- [Assigning issues](#assigning-issues)
-- [Be kind](#be-kind)
-- [Bugs](#bugs)
- - [Regressions](#regressions)
- - [Managing bugs](#managing-bugs)
-- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
-- [Copy & paste responses](#copy--paste-responses)
- - [Improperly formatted issue](#improperly-formatted-issue)
- - [Issue report for old version](#issue-report-for-old-version)
- - [Support requests and configuration questions](#support-requests-and-configuration-questions)
- - [Code format](#code-format)
- - [Issue fixed in newer version](#issue-fixed-in-newer-version)
- - [Improperly formatted merge request](#improperly-formatted-merge-request)
- - [Seeking community contributions](#seeking-community-contributions)
- - [Only accepting merge requests with green tests](#only-accepting-merge-requests-with-green-tests)
-
-<!-- END doctoc generated TOC please keep comment here to allow auto update -->
-
----
-
-## Purpose of describing the contributing process
-
-Below we describe the contributing process to GitLab for two reasons:
-
-1. Contributors know what to expect from maintainers (possible responses, friendly
- treatment, etc.)
-1. Maintainers know what to expect from contributors (use the latest version,
- ensure that the issue is addressed, friendly treatment, etc.).
-
-- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
-
-## Common actions
-
-### Merge request coaching
-
-Several people from the [GitLab team][team] are helping community members to get
-their contributions accepted by meeting our [Definition of done][done].
-
-What you can expect from them is described at https://about.gitlab.com/job-families/expert/merge-request-coach/.
-
-### Milestones on community contribution issues
-
-The milestone of an issue that is currently being worked on by a community contributor
-should not be set to a named GitLab milestone (e.g. 11.7, 11.8), until the associated
-merge request is very close to being merged, and we will likely know in which named
-GitLab milestone the issue will land. There are many factors that influence when
-a community contributor finishes an issue, or even at all. So we should set this
-milestone only when we have more certainty.
-
-Note this only applies to issues currently assigned to community contributors. For
-issues assigned to GitLabbers, we are [ambitious in assigning milestones to issues](https://about.gitlab.com/direction/#how-we-plan-releases).
-
-## Assigning issues
-
-If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
-
-## Be kind
-
-Be kind to people trying to contribute. Be aware that people may be a non-native
-English speaker, they might not understand things or they might be very
-sensitive as to how you word things. Use Emoji to express your feelings (heart,
-star, smile, etc.). Some good tips about code reviews can be found in our
-[Code Review Guidelines].
-
-[Code Review Guidelines]: https://docs.gitlab.com/ee/development/code_review.html
-
-## Feature flags
-
-Overview and details of feature flag processes in development of GitLab itself is described in [feature flags process documentation](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/).
-
-Guides on how to include feature flags in your backend/frontend code while developing GitLab are described in [developing with feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags).
-
-Getting access and how to expose the feature to users is detailed in [controlling feature flags documentation](https://docs.gitlab.com/ee/development/feature_flags/controls.html).
-
-## Feature proposals from the 22nd to the 1st
-
-To allow the Product and Engineering teams time to discuss issues that will be placed into an upcoming milestone,
-Product Managers must have their proposal for that milestone ready by the 22nd of each month.
-
-This proposal will be shared with Engineering for discussion, feedback, and planning.
-The plan for the upcoming milestone must be finalized by the 1st of the month, one week before kickoff on the 8th.
-
-## Bugs
-
-A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling the product requirements.
-
-The level of impact of a ~bug can vary from blocking a whole functionality
-or a feature usability bug. A bug should always be linked to a severity level.
-Refer to our [severity levels](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#severity-labels)
-
-Whether the bug is also a regression or not, the triage process should start as soon as possible.
-Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed.
-
-### Regressions
-
-A ~regression implies that a previously **verified working functionality** no longer works.
-Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress.
-The label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule.
-
-The regression label does not apply to ~bugs for new features for which functionality was **never verified as working**.
-These, by definition, are not regressions.
-
-A regression should always have the `regression:xx.x` label on it to designate when it was introduced.
-
-Regressions should be considered high priority issues that should be solved as soon as possible, especially if they have severe impact on users.
-
-### Managing bugs
-
-**Prioritization:** We give higher priority to regressions on features that worked in the last recent monthly release and the current release candidates.
-
-When a bug is found:
-1. Create an issue describing the problem in the most detailed way possible.
-1. If possible, provide links to real examples and how to reproduce the problem.
-1. Label the issue properly, by respecting the [Partial triage level](https://about.gitlab.com/handbook/engineering/issue-triage/#partial-triage).
-1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#severity-labels) and [Priority label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#priority-labels).
-The counterpart Product Manager is included to weigh-in on prioritization as needed.
-1. If the ~bug is **NOT** a regression:
- 1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied.
-1. If the bug is a ~regression:
- 1. Determine the release that the regression affects and add the corresponding `regression:xx.x` label.
- 1. If the affected release version can't be determined, add the generic ~regression label for the time being.
- 1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, it's recommended to schedule the fix for the current milestone.
- 1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above.
- 1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release**
- 1. If the regression is an ~S1 severity, it's recommended to schedule the fix for the current milestone. We would like to fix the highest severity regression as soon as we can.
- 1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of the Engineering Manager and Product Manager.
-
-## Release retrospective and kickoff
-
-- [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective)
-- [Kickoff](https://about.gitlab.com/handbook/engineering/workflow/#kickoff)
-
-## Copy & paste responses
-
-### Improperly formatted issue
-
-```
-Thanks for the issue report. Please reformat your issue to conform to the
-[contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines).
-```
-
-### Issue report for old version
-
-```
-Thanks for the issue report but we only support issues for the latest stable version of GitLab.
-I'm closing this issue but if you still experience this problem in the latest stable version,
-please open a new issue (but also reference the old issue(s)).
-Make sure to also include the necessary debugging information conforming to the issue tracker
-guidelines found in our [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines).
-```
-
-### Support requests and configuration questions
-
-```
-Thanks for your interest in GitLab. We don't use the issue tracker for support
-requests and configuration questions. Please check our
-[getting help](https://about.gitlab.com/getting-help/) page to see all of the available
-support options. Also, have a look at the [contribution guidelines](https://docs.gitlab.com/ee/development/contributing/index.html)
-for more information.
-```
-
-### Code format
-
-```
-Please use \`\`\` to format console output, logs, and code as it's very hard to read otherwise.
-```
-
-### Issue fixed in newer version
-
-```
-Thanks for the issue report. This issue has already been fixed in newer versions of GitLab.
-Due to the size of this project and our limited resources we are only able to support the
-latest stable release as outlined in our [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html).
-In order to get this bug fix and enjoy many new features please
-[upgrade](https://gitlab.com/gitlab-org/gitlab/tree/master/doc/update).
-If you still experience issues at that time please open a new issue following our issue
-tracker guidelines found in the [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines).
-```
-
-### Improperly formatted merge request
-
-```
-Thanks for your interest in improving the GitLab codebase!
-Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/development/contributing/merge_request_workflow.md#merge-request-guidelines).
-```
-
-### Seeking community contributions
-
-```
-Is there an issue on the
-[issue tracker](https://gitlab.com/gitlab-org/gitlab/issues) that is
-similar to this? Could you please link it here?
-Please be aware that new functionality that is not marked
-[`Seeking community contributions`](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#label-for-community-contributors)
-might not make it into GitLab.
-```
-
-### Only accepting merge requests with green tests
-
-```
-We can only accept a merge request if all the tests are green. I've just
-restarted the build. When the tests are still not passing after this restart and
-you're sure that is does not have anything to do with your code changes, please
-rebase with master to see if that solves the issue.
-```
-
-[team]: https://about.gitlab.com/team/
-[done]: https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done
-[automatic_ce_ee_merge]: https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html
-[ee_features]: https://docs.gitlab.com/ee/development/ee_features.html
+<!-- This redirect file can be deleted after <2023-10-01>. -->
diff --git a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
index 76709296c89..cd64579a360 100644
--- a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
+++ b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue
@@ -55,7 +55,22 @@ export default {
},
computed: {
filteredFields() {
- return this.showRole ? FIELDS : FIELDS.filter((field) => field.key !== 'role');
+ const ignoredFields = [];
+
+ // Show 'action' column only when there are no active tokens or when some of them have a revokePath
+ const showAction =
+ this.activeAccessTokens.length === 0 ||
+ this.activeAccessTokens.some((token) => token.revokePath);
+
+ if (!showAction) {
+ ignoredFields.push('action');
+ }
+
+ if (!this.showRole) {
+ ignoredFields.push('role');
+ }
+
+ return FIELDS.filter(({ key }) => !ignoredFields.includes(key));
},
header() {
return sprintf(this.$options.i18n.header, {
diff --git a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
index 3e87088e77e..7d2b9cd3d42 100644
--- a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
+++ b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
@@ -15,11 +15,20 @@ export default {
'Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started.',
),
btnText: __('Configure pipeline'),
+ externalCiNote: __("This project's pipeline configuration is located outside this repository"),
+ externalCiInstructions: __(
+ 'To edit the pipeline configuration, you must go to the project or external site that hosts the file.',
+ ),
},
inject: {
emptyStateIllustrationPath: {
default: '',
},
+ usesExternalConfig: {
+ default: false,
+ type: Boolean,
+ required: false,
+ },
},
methods: {
createEmptyConfigFile() {
@@ -33,22 +42,31 @@ export default {
<pipeline-editor-file-nav v-on="$listeners" />
<div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
<img :src="emptyStateIllustrationPath" />
- <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
- <p class="gl-mt-3">
- <gl-sprintf :message="$options.i18n.body">
- <template #code="{ content }">
- <code>{{ content }}</code>
- </template>
- </gl-sprintf>
- </p>
- <gl-button
- variant="confirm"
- class="gl-mt-3"
- data-qa-selector="create_new_ci_button"
- @click="createEmptyConfigFile"
+ <div
+ v-if="usesExternalConfig"
+ class="gl-display-flex gl-flex-direction-column gl-align-items-center"
>
- {{ $options.i18n.btnText }}
- </gl-button>
+ <h1 class="gl-font-size-h1">{{ $options.i18n.externalCiNote }}</h1>
+ <p class="gl-mt-3">{{ $options.i18n.externalCiInstructions }}</p>
+ </div>
+ <div v-else class="gl-display-flex gl-flex-direction-column gl-align-items-center">
+ <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
+ <p class="gl-mt-3">
+ <gl-sprintf :message="$options.i18n.body">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <gl-button
+ variant="confirm"
+ class="gl-mt-3"
+ data-qa-selector="create_new_ci_button"
+ @click="createEmptyConfigFile"
+ >
+ {{ $options.i18n.btnText }}
+ </gl-button>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index 13dad0b2459..6d91c339833 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { parseBoolean } from '~/lib/utils/common_utils';
import { EDITOR_APP_STATUS_LOADING } from './constants';
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants';
import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql';
@@ -42,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectNamespace,
simulatePipelineHelpPagePath,
totalBranches,
+ usesExternalConfig,
validateTabIllustrationPath,
ymlHelpPagePath,
} = el.dataset;
@@ -133,6 +135,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
projectNamespace,
simulatePipelineHelpPagePath,
totalBranches: parseInt(totalBranches, 10),
+ usesExternalConfig: parseBoolean(usesExternalConfig),
validateTabIllustrationPath,
ymlHelpPagePath,
},
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 548769eb214..ff848a973e3 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -40,7 +40,7 @@ export default {
PipelineEditorHome,
PipelineEditorMessages,
},
- inject: ['ciConfigPath', 'newMergeRequestPath', 'projectFullPath'],
+ inject: ['ciConfigPath', 'newMergeRequestPath', 'projectFullPath', 'usesExternalConfig'],
data() {
return {
ciConfigData: {},
@@ -397,7 +397,7 @@ export default {
<div class="gl-mt-4 gl-relative" data-qa-selector="pipeline_editor_app">
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
<pipeline-editor-empty-state
- v-else-if="showStartScreen"
+ v-else-if="showStartScreen || usesExternalConfig"
@createEmptyConfigFile="setNewEmptyCiConfigFile"
@refetchContent="refetchContent"
/>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
new file mode 100644
index 00000000000..1fd87b9897c
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
@@ -0,0 +1,35 @@
+import { s__ } from '~/locale';
+
+export const I18N = {
+ manageProtectionsLinkTitle: s__('BranchRules|Manage in Protected Branches'),
+ targetBranch: s__('BranchRules|Target Branch'),
+ branchNameOrPattern: s__('BranchRules|Branch name or pattern'),
+ branch: s__('BranchRules|Target Branch'),
+ allBranches: s__('BranchRules|All branches'),
+ protectBranchTitle: s__('BranchRules|Protect branch'),
+ protectBranchDescription: s__(
+ 'BranchRules|Keep stable branches secure and force developers to use merge requests. %{linkStart}What are protected branches?%{linkEnd}',
+ ),
+ wildcardsHelpText: s__(
+ 'BranchRules|%{linkStart}Wildcards%{linkEnd} such as *-stable or production/ are supported',
+ ),
+ forcePushTitle: s__('BranchRules|Force push'),
+ allowForcePushDescription: s__(
+ 'BranchRules|All users with push access are allowed to force push.',
+ ),
+ disallowForcePushDescription: s__('BranchRules|Force push is not allowed.'),
+ approvalsTitle: s__('BranchRules|Approvals'),
+ statusChecksTitle: s__('BranchRules|Status checks'),
+ allowedToPushHeader: s__('BranchRules|Allowed to push (%{total})'),
+ allowedToMergeHeader: s__('BranchRules|Allowed to merge (%{total})'),
+ noData: s__('BranchRules|No data to display'),
+};
+
+export const BRANCH_PARAM_NAME = 'branch';
+
+export const ALL_BRANCHES_WILDCARD = '*';
+
+export const WILDCARDS_HELP_PATH =
+ 'user/project/protected_branches#configure-multiple-protected-branches-by-using-a-wildcard';
+
+export const PROTECTED_BRANCHES_HELP_PATH = 'user/project/protected_branches';
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
index 58938b6bf96..6534ff883a6 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
@@ -1,33 +1,172 @@
<script>
-import { s__ } from '~/locale';
+import { GlSprintf, GlLink, GlLoadingIcon } from '@gitlab/ui';
+import { sprintf } from '~/locale';
import { getParameterByName } from '~/lib/utils/url_utility';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import branchRulesQuery from '../../queries/branch_rules_details.query.graphql';
+import Protection from './protection.vue';
+import {
+ I18N,
+ ALL_BRANCHES_WILDCARD,
+ BRANCH_PARAM_NAME,
+ WILDCARDS_HELP_PATH,
+ PROTECTED_BRANCHES_HELP_PATH,
+} from './constants';
+
+const wildcardsHelpDocLink = helpPagePath(WILDCARDS_HELP_PATH);
+const protectedBranchesHelpDocLink = helpPagePath(PROTECTED_BRANCHES_HELP_PATH);
export default {
name: 'RuleView',
- i18n: { branch: s__('BranchRules|Target Branch') },
- components: {},
- props: {
+ i18n: I18N,
+ wildcardsHelpDocLink,
+ protectedBranchesHelpDocLink,
+ components: { Protection, GlSprintf, GlLink, GlLoadingIcon },
+ inject: {
projectPath: {
- type: String,
- required: true,
+ default: '',
+ },
+ protectedBranchesPath: {
+ default: '',
+ },
+ },
+ apollo: {
+ project: {
+ query: branchRulesQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update({ project: { branchRules } }) {
+ this.branchProtection = branchRules.nodes.find(
+ (rule) => rule.name === this.branch,
+ )?.branchProtection;
+ },
},
},
data() {
return {
- branch: getParameterByName('branch'),
- protected: getParameterByName('protected'),
+ branch: getParameterByName(BRANCH_PARAM_NAME),
+ branchProtection: {},
};
},
+ computed: {
+ forcePushDescription() {
+ return this.branchProtection?.allowForcePush
+ ? this.$options.i18n.allowForcePushDescription
+ : this.$options.i18n.disallowForcePushDescription;
+ },
+ mergeAccessLevels() {
+ const { mergeAccessLevels } = this.branchProtection || {};
+ return this.getAccessLevels(mergeAccessLevels);
+ },
+ pushAccessLevels() {
+ const { pushAccessLevels } = this.branchProtection || {};
+ return this.getAccessLevels(pushAccessLevels);
+ },
+ allowedToMergeHeader() {
+ return sprintf(this.$options.i18n.allowedToMergeHeader, {
+ total: this.mergeAccessLevels.total,
+ });
+ },
+ allowedToPushHeader() {
+ return sprintf(this.$options.i18n.allowedToPushHeader, {
+ total: this.pushAccessLevels.total,
+ });
+ },
+ allBranches() {
+ return this.branch === ALL_BRANCHES_WILDCARD;
+ },
+ allBranchesLabel() {
+ return this.$options.i18n.allBranches;
+ },
+ branchTitle() {
+ return this.allBranches
+ ? this.$options.i18n.targetBranch
+ : this.$options.i18n.branchNameOrPattern;
+ },
+ },
+ methods: {
+ getAccessLevels(accessLevels = {}) {
+ const total = accessLevels.edges?.length;
+ const accessLevelTypes = { total, users: [], groups: [], roles: [] };
+
+ accessLevels.edges?.forEach(({ node }) => {
+ if (node.user) {
+ const src = node.user.avatarUrl;
+ accessLevelTypes.users.push({ src, ...node.user });
+ } else if (node.group) {
+ accessLevelTypes.groups.push(node);
+ } else {
+ accessLevelTypes.roles.push(node);
+ }
+ });
+
+ return accessLevelTypes;
+ },
+ },
};
</script>
<template>
- <div>
- <div class="gl-display-flex gl-flex-direction-column gl-pt-3">
- <strong>{{ $options.i18n.branch }}</strong>
- <span class="gl-font-monospace gl-mt-2" data-testid="branch">{{ branch }}</span>
+ <gl-loading-icon v-if="$apollo.loading" />
+ <div v-else-if="!branchProtection">{{ $options.i18n.noData }}</div>
+ <div v-else>
+ <strong data-testid="branch-title">{{ branchTitle }}</strong>
+ <p v-if="!allBranches" class="gl-mb-3 gl-text-gray-400">
+ <gl-sprintf :message="$options.i18n.wildcardsHelpText">
+ <template #link="{ content }">
+ <gl-link :href="$options.wildcardsHelpDocLink">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <div v-if="allBranches" class="gl-mt-2" data-testid="branch">
+ {{ allBranchesLabel }}
</div>
+ <code v-else class="gl-mt-2" data-testid="branch">{{ branch }}</code>
+
+ <h4 class="gl-mb-1 gl-mt-5">{{ $options.i18n.protectBranchTitle }}</h4>
+ <gl-sprintf :message="$options.i18n.protectBranchDescription">
+ <template #link="{ content }">
+ <gl-link :href="$options.protectedBranchesHelpDocLink">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+
+ <!-- Allowed to push -->
+ <protection
+ class="gl-mt-3"
+ :header="allowedToPushHeader"
+ :header-link-title="$options.i18n.manageProtectionsLinkTitle"
+ :header-link-href="protectedBranchesPath"
+ :roles="pushAccessLevels.roles"
+ :users="pushAccessLevels.users"
+ :groups="pushAccessLevels.groups"
+ />
+
+ <!-- Force push -->
+ <strong>{{ $options.i18n.forcePushTitle }}</strong>
+ <p>{{ forcePushDescription }}</p>
+
+ <!-- Allowed to merge -->
+ <protection
+ :header="allowedToMergeHeader"
+ :header-link-title="$options.i18n.manageProtectionsLinkTitle"
+ :header-link-href="protectedBranchesPath"
+ :roles="mergeAccessLevels.roles"
+ :users="mergeAccessLevels.users"
+ :groups="mergeAccessLevels.groups"
+ />
+
+ <!-- Approvals -->
+ <!-- Follow-up: add approval section (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
- <!-- TODO: List branch rule details (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
+ <!-- Status checks -->
+ <!-- Follow-up: add status checks section (https://gitlab.com/gitlab-org/gitlab/-/issues/372362) -->
</div>
</template>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
index ec70ab88870..8434b7bfce5 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection.vue
@@ -58,7 +58,7 @@ export default {
<template #header>
<div class="gl-display-flex gl-justify-content-space-between">
<strong>{{ header }}</strong>
- <gl-link :href="headerLinkHref" target="_blank">{{ headerLinkTitle }}</gl-link>
+ <gl-link :href="headerLinkHref">{{ headerLinkTitle }}</gl-link>
</div>
</template>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
index 56be0198574..2509c2538b2 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_row.vue
@@ -45,6 +45,9 @@ export default {
this.users.length - this.$options.MAX_VISIBLE_AVATARS,
);
},
+ commaSeparateList() {
+ return this.accessLevels.length > 1;
+ },
},
};
</script>
@@ -80,6 +83,7 @@ export default {
</gl-avatars-inline>
<div v-for="(item, index) in accessLevels" :key="index" data-testid="access-level">
+ <span v-if="commaSeparateList && index > 0" data-testid="comma-separator">,</span>
{{ item.accessLevelDescription }}
</div>
</div>
diff --git a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
index 10de5f12757..39164063d05 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
+++ b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import RuleEdit from './components/edit/index.vue';
+import View from './components/view/index.vue';
export default function mountBranchRules(el) {
if (!el) {
@@ -14,13 +14,17 @@ export default function mountBranchRules(el) {
defaultClient: createDefaultClient(),
});
- const { projectPath } = el.dataset;
+ const { projectPath, protectedBranchesPath } = el.dataset;
return new Vue({
el,
apolloProvider,
+ provide: {
+ projectPath,
+ protectedBranchesPath,
+ },
render(h) {
- return h(RuleEdit, { props: { projectPath } });
+ return h(View);
},
});
}
diff --git a/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
new file mode 100644
index 00000000000..3ac165498a1
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
@@ -0,0 +1,50 @@
+query getBranchRulesDetails($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ id
+ branchRules {
+ nodes {
+ name
+ branchProtection {
+ allowForcePush
+ codeOwnerApprovalRequired
+ mergeAccessLevels {
+ edges {
+ node {
+ accessLevel
+ accessLevelDescription
+ group {
+ id
+ avatarUrl
+ }
+ user {
+ id
+ name
+ avatarUrl
+ webUrl
+ }
+ }
+ }
+ }
+ pushAccessLevels {
+ edges {
+ node {
+ accessLevel
+ accessLevelDescription
+ group {
+ id
+ avatarUrl
+ }
+ user {
+ id
+ name
+ avatarUrl
+ webUrl
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
index 65e0c87e3f3..94793a535cc 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
@@ -35,10 +35,9 @@ export default {
},
},
},
- props: {
+ inject: {
projectPath: {
- type: String,
- required: true,
+ default: '',
},
},
data() {
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
index 68750318029..2b88f8561d7 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
@@ -1,10 +1,11 @@
<script>
-import { GlBadge } from '@gitlab/ui';
+import { GlBadge, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
export const i18n = {
defaultLabel: s__('BranchRules|default'),
protectedLabel: s__('BranchRules|protected'),
+ detailsButtonLabel: s__('BranchRules|Details'),
};
export default {
@@ -12,6 +13,12 @@ export default {
i18n,
components: {
GlBadge,
+ GlButton,
+ },
+ inject: {
+ branchRulesPath: {
+ default: '',
+ },
},
props: {
name: {
@@ -38,24 +45,30 @@ export default {
hasApprovalDetails() {
return this.approvalDetails && this.approvalDetails.length;
},
+ detailsPath() {
+ return `${this.branchRulesPath}?branch=${this.name}`;
+ },
},
};
</script>
<template>
- <div class="gl-border-b gl-pt-5 gl-pb-5">
- <strong class="gl-font-monospace">{{ name }}</strong>
+ <div class="gl-border-b gl-pt-5 gl-pb-5 gl-display-flex gl-justify-content-space-between">
+ <div>
+ <strong class="gl-font-monospace">{{ name }}</strong>
- <gl-badge v-if="isDefault" variant="info" size="sm" class="gl-ml-2">{{
- $options.i18n.defaultLabel
- }}</gl-badge>
+ <gl-badge v-if="isDefault" variant="info" size="sm" class="gl-ml-2">{{
+ $options.i18n.defaultLabel
+ }}</gl-badge>
- <gl-badge v-if="isProtected" variant="success" size="sm" class="gl-ml-2">{{
- $options.i18n.protectedLabel
- }}</gl-badge>
+ <gl-badge v-if="isProtected" variant="success" size="sm" class="gl-ml-2">{{
+ $options.i18n.protectedLabel
+ }}</gl-badge>
- <ul v-if="hasApprovalDetails" class="gl-pl-6 gl-mt-2 gl-mb-0 gl-text-gray-500">
- <li v-for="(detail, index) in approvalDetails" :key="index">{{ detail }}</li>
- </ul>
+ <ul v-if="hasApprovalDetails" class="gl-pl-6 gl-mt-2 gl-mb-0 gl-text-gray-500">
+ <li v-for="(detail, index) in approvalDetails" :key="index">{{ detail }}</li>
+ </ul>
+ </div>
+ <gl-button :href="detailsPath"> {{ $options.i18n.detailsButtonLabel }}</gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js
index 35322e2e466..042be089e09 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/mount_branch_rules.js
@@ -12,17 +12,17 @@ const apolloProvider = new VueApollo({
export default function mountBranchRules(el) {
if (!el) return null;
- const { projectPath } = el.dataset;
+ const { projectPath, branchRulesPath } = el.dataset;
return new Vue({
el,
apolloProvider,
+ provide: {
+ projectPath,
+ branchRulesPath,
+ },
render(createElement) {
- return createElement(BranchRulesApp, {
- props: {
- projectPath,
- },
- });
+ return createElement(BranchRulesApp);
},
});
}
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index 147d7a92d57..68fe701c7e1 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -17,7 +17,6 @@
@import './pages/pipelines';
@import './pages/profile';
@import './pages/projects';
-@import './pages/prometheus';
@import './pages/registry';
@import './pages/search';
@import './pages/service_desk';
diff --git a/app/assets/stylesheets/page_bundles/incidents.scss b/app/assets/stylesheets/page_bundles/incidents.scss
new file mode 100644
index 00000000000..de246fa14b9
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/incidents.scss
@@ -0,0 +1,73 @@
+@import 'mixins_and_variables_and_functions';
+
+.issuable-discussion.incident-timeline-events {
+ .main-notes-list::before {
+ content: none;
+ }
+
+ .timeline-event-note {
+ p {
+ margin-bottom: 0;
+ font-size: 0.875rem;
+ }
+ }
+}
+
+/**
+ * We have a very specific design proposal where we cannot
+ * use `vertical-line` mixin as it is and have to use
+ * custom styles, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81284#note_904867444
+ */
+.timeline-entry-vertical-line {
+ &::before,
+ &::after {
+ content: '';
+ border-left: 2px solid $gray-50;
+ position: absolute;
+ left: 20px;
+ height: calc(100% + #{$gl-spacing-scale-5});
+ top: -#{$gl-spacing-scale-5};
+ }
+
+ &:first-child::before {
+ content: none;
+ }
+
+ &:first-child {
+ &::after {
+ top: $gl-spacing-scale-5;
+ height: calc(100% + #{$gl-spacing-scale-5});
+ }
+ }
+
+ &:last-child,
+ &.create-timeline-event {
+ &::before {
+ top: - #{$gl-spacing-scale-5} !important; // Override default positioning
+ @include gl-h-8;
+ }
+
+ &::after {
+ content: none;
+ }
+ }
+}
+
+.timeline-entry:not(:last-child) {
+ .timeline-event-border {
+ @include gl-pb-5;
+ @include gl-border-gray-50;
+ @include gl-border-1;
+ @include gl-border-b-solid;
+ }
+}
+
+.timeline-group:last-child {
+ .timeline-entry:last-child,
+ .create-timeline-event {
+ .timeline-event-bottom-border {
+ @include gl-border-b;
+ @include gl-pt-5;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/page_bundles/prometheus.scss
index 71cbd7d9613..055036d757d 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/page_bundles/prometheus.scss
@@ -1,3 +1,5 @@
+@import 'mixins_and_variables_and_functions';
+
.prometheus-graphs {
.dropdown-buttons {
> div {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index e77e30c532e..6070311dcb6 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -856,24 +856,6 @@
}
}
-.issuable-todo-btn {
- .gl-spinner {
- display: none;
- }
-
- &.is-loading {
- .gl-spinner {
- display: inline-block;
- }
-
- &.sidebar-collapsed-icon {
- .issuable-todo-inner {
- display: none;
- }
- }
- }
-}
-
/*
* Following overrides are done to prevent
* legacy dropdown styles from influencing
@@ -932,85 +914,3 @@
}
}
}
-
-.icon-overlap-and-shadow {
- filter:
- drop-shadow(0 1px 0.5px #fff)
- drop-shadow(1px 0 0.5px #fff)
- drop-shadow(0 -1px 0.5px #fff)
- drop-shadow(-1px 0 0.5px #fff);
- margin-right: -7px;
- z-index: 1;
-}
-
-.issuable-discussion.incident-timeline-events {
- .main-notes-list::before {
- content: none;
- }
-
- .timeline-event-note {
- p {
- margin-bottom: 0;
- font-size: 0.875rem;
- }
- }
-}
-
-/**
- * We have a very specific design proposal where we cannot
- * use `vertical-line` mixin as it is and have to use
- * custom styles, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81284#note_904867444
- */
-.timeline-entry-vertical-line {
- &::before,
- &::after {
- content: '';
- border-left: 2px solid $gray-50;
- position: absolute;
- left: 20px;
- height: calc(100% + #{$gl-spacing-scale-5});
- top: -#{$gl-spacing-scale-5};
- }
-
- &:first-child::before {
- content: none;
- }
-
- &:first-child {
- &::after {
- top: $gl-spacing-scale-5;
- height: calc(100% + #{$gl-spacing-scale-5});
- }
- }
-
- &:last-child,
- &.create-timeline-event {
- &::before {
- top: - #{$gl-spacing-scale-5} !important; // Override default positioning
- @include gl-h-8;
- }
-
- &::after {
- content: none;
- }
- }
-}
-
-.timeline-entry:not(:last-child) {
- .timeline-event-border {
- @include gl-pb-5;
- @include gl-border-gray-50;
- @include gl-border-1;
- @include gl-border-b-solid;
- }
-}
-
-.timeline-group:last-child {
- .timeline-entry:last-child,
- .create-timeline-event {
- .timeline-event-bottom-border {
- @include gl-border-b;
- @include gl-pt-5;
- }
- }
-}
diff --git a/app/helpers/ci/pipeline_editor_helper.rb b/app/helpers/ci/pipeline_editor_helper.rb
index d00301678dd..99a92ba9b59 100644
--- a/app/helpers/ci/pipeline_editor_helper.rb
+++ b/app/helpers/ci/pipeline_editor_helper.rb
@@ -11,7 +11,6 @@ module Ci
def js_pipeline_editor_data(project)
initial_branch = params[:branch_name]
latest_commit = project.repository.commit(initial_branch) || project.commit
- commit_sha = latest_commit ? latest_commit.sha : ''
total_branches = project.repository_exists? ? project.repository.branch_count : 0
{
@@ -27,17 +26,26 @@ module Ci
"lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available-message'),
"needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'),
"new-merge-request-path" => namespace_project_new_merge_request_path,
- "pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(commit_sha) : '',
+ "pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(latest_commit.sha) : '',
"pipeline-page-path" => project_pipelines_path(project),
"project-path" => project.path,
"project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path,
"simulate-pipeline-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'simulate-a-cicd-pipeline'),
"total-branches" => total_branches,
+ "uses-external-config" => uses_external_config?(project) ? 'true' : 'false',
"validate-tab-illustration-path" => image_path('illustrations/project-run-CICD-pipelines-sm.svg'),
"yml-help-page-path" => help_page_path('ci/yaml/index')
}
end
+
+ private
+
+ def uses_external_config?(project)
+ ci_config_source = Gitlab::Ci::ProjectConfig.new(project: project, sha: nil).source
+
+ [:external_project_source, :remote_source].include?(ci_config_source)
+ end
end
end
diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb
index a67771116b9..c93c8dd8d76 100644
--- a/app/helpers/ci/pipelines_helper.rb
+++ b/app/helpers/ci/pipelines_helper.rb
@@ -69,7 +69,8 @@ module Ci
end
def has_pipeline_badges?(pipeline)
- pipeline.child? ||
+ pipeline.schedule? ||
+ pipeline.child? ||
pipeline.latest? ||
pipeline.merge_train_pipeline? ||
pipeline.has_yaml_errors? ||
diff --git a/app/models/project.rb b/app/models/project.rb
index aca61f645da..3d026351b4f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -477,6 +477,7 @@ class Project < ApplicationRecord
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :forward_deployment_enabled, :forward_deployment_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
+ delegate :inbound_job_token_scope_enabled, :inbound_job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true
delegate :opt_in_jwt, :opt_in_jwt=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :allow_fork_pipelines_to_run_in_parent_project, :allow_fork_pipelines_to_run_in_parent_project=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
@@ -2720,6 +2721,7 @@ class Project < ApplicationRecord
ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci]
end
+ # DO NOT USE. This method will be deprecated soon
def uses_external_project_ci_config?
!!(ci_config_path =~ %r{@.+/.+})
end
@@ -2844,6 +2846,7 @@ class Project < ApplicationRecord
repository.gitlab_ci_yml_for(sha, ci_config_path_or_default)
end
+ # DO NOT USE. This method will be deprecated soon
def ci_config_external_project
Project.find_by_full_path(ci_config_path.split('@', 2).last)
end
@@ -2892,6 +2895,12 @@ class Project < ApplicationRecord
ci_cd_settings.job_token_scope_enabled?
end
+ def ci_inbound_job_token_scope_enabled?
+ return false unless ci_cd_settings
+
+ ci_cd_settings.inbound_job_token_scope_enabled?
+ end
+
def restrict_user_defined_variables?
return false unless ci_cd_settings
diff --git a/app/models/user.rb b/app/models/user.rb
index 9fa9419cd37..d64a52ff7b9 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -235,6 +235,7 @@ class User < ApplicationRecord
has_one :user_highest_role
has_one :user_canonical_email
has_one :credit_card_validation, class_name: '::Users::CreditCardValidation'
+ has_one :phone_number_validation, class_name: '::Users::PhoneNumberValidation'
has_one :atlassian_identity, class_name: 'Atlassian::Identity'
has_one :banned_user, class_name: '::Users::BannedUser'
diff --git a/app/models/users/phone_number_validation.rb b/app/models/users/phone_number_validation.rb
new file mode 100644
index 00000000000..f6123c01fd0
--- /dev/null
+++ b/app/models/users/phone_number_validation.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Users
+ class PhoneNumberValidation < ApplicationRecord
+ self.primary_key = :user_id
+ self.table_name = 'user_phone_number_validations'
+
+ belongs_to :user, foreign_key: :user_id
+ belongs_to :banned_user, class_name: '::Users::BannedUser', foreign_key: :user_id
+
+ validates :country,
+ presence: true,
+ length: { maximum: 3 }
+
+ validates :international_dial_code,
+ presence: true,
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: 1,
+ less_than_or_equal_to: 999
+ }
+
+ validates :phone_number,
+ presence: true,
+ format: {
+ with: /\A\d+\Z/,
+ message: -> (object, data) { _('can contain only digits') }
+ },
+ length: { maximum: 12 }
+
+ validates :telesign_reference_xid,
+ length: { maximum: 255 }
+
+ def self.related_to_banned_user?(international_dial_code, phone_number)
+ joins(:banned_user).where(
+ international_dial_code: international_dial_code,
+ phone_number: phone_number
+ ).exists?
+ end
+ end
+end
diff --git a/app/models/wiki.rb b/app/models/wiki.rb
index fac79a8194a..afdac86d9c7 100644
--- a/app/models/wiki.rb
+++ b/app/models/wiki.rb
@@ -229,7 +229,7 @@ class Wiki
find_page(SIDEBAR, version)
end
- def find_file(name, version = 'HEAD', load_content: true)
+ def find_file(name, version = default_branch, load_content: true)
data_limit = load_content ? -1 : 0
blobs = repository.blobs_at([[version, name]], blob_size_limit: data_limit)
@@ -423,7 +423,7 @@ class Wiki
escaped_title = Regexp.escape(sluggified_title(title))
regex = Regexp.new("^#{escaped_title}\.#{ALLOWED_EXTENSIONS_REGEX}$", 'i')
- repository.ls_files('HEAD').any? { |s| s =~ regex }
+ repository.ls_files(default_branch).any? { |s| s =~ regex }
end
def raise_duplicate_page_error!
@@ -473,11 +473,11 @@ class Wiki
end
def check_page_historical(path, commit)
- repository.last_commit_for_path('HEAD', path).id != commit.id
+ repository.last_commit_for_path(default_branch, path)&.id != commit&.id
end
def find_page_with_repository_rpcs(title, version, load_content: true)
- version = version.presence || 'HEAD'
+ version = version.presence || default_branch
path = find_matched_file(title, version)
return if path.blank?
@@ -493,7 +493,7 @@ class Wiki
path: sluggified_title(path),
raw_data: blob.data,
name: canonicalize_filename(path),
- historical: version == 'HEAD' ? false : check_page_historical(path, commit),
+ historical: version == default_branch ? false : check_page_historical(path, commit),
version: Gitlab::Git::WikiPageVersion.new(commit, format)
)
WikiPage.new(self, page)
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 63c60f5a89e..5cdd1fdadcb 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -138,7 +138,7 @@ class WikiPage
default_per_page = Kaminari.config.default_per_page
offset = [options[:page].to_i - 1, 0].max * options.fetch(:per_page, default_per_page)
- wiki.repository.commits('HEAD',
+ wiki.repository.commits(wiki.default_branch,
path: page.path,
limit: options.fetch(:limit, default_per_page),
offset: offset)
@@ -147,7 +147,7 @@ class WikiPage
def count_versions
return [] unless persisted?
- wiki.wiki.count_page_versions(page.path)
+ wiki.repository.count_commits(ref: wiki.default_branch, path: page.path)
end
def last_version
diff --git a/app/uploaders/object_storage/cdn/google_cdn.rb b/app/uploaders/object_storage/cdn/google_cdn.rb
index ea7683f131c..d34ed8ac897 100644
--- a/app/uploaders/object_storage/cdn/google_cdn.rb
+++ b/app/uploaders/object_storage/cdn/google_cdn.rb
@@ -41,7 +41,7 @@ module ObjectStorage
private
def config_valid?
- [key_name, decoded_key, cdn_url].all?(&:present?)
+ [key_name, decoded_key, cdn_url].all?(&:present?) && cdn_url.start_with?('https://')
end
def key_name
diff --git a/app/views/clusters/clusters/_health.html.haml b/app/views/clusters/clusters/_health.html.haml
index 75609465eb3..50facdf91a2 100644
--- a/app/views/clusters/clusters/_health.html.haml
+++ b/app/views/clusters/clusters/_health.html.haml
@@ -1,3 +1,5 @@
+- add_page_specific_style 'page_bundles/prometheus'
+
%section.settings.no-animate.expanded.cluster-health-graphs.qa-cluster-health-section#cluster-health
- if @cluster&.integration_prometheus_available?
#prometheus-graphs{ data: @cluster.health_data(clusterable) }
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 4139be053f8..9fd542e0cfb 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -10,7 +10,7 @@
%ul.blob-commit-info
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref
- = render_if_exists 'projects/blob/owners', blob: blob
+ #js-code-owners{ data: { blob_path: blob.path, project_path: @project.full_path, branch: @ref } }
= render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
diff --git a/app/views/projects/branch_rules/_show.html.haml b/app/views/projects/branch_rules/_show.html.haml
index 46665fdb450..27525b441ab 100644
--- a/app/views/projects/branch_rules/_show.html.haml
+++ b/app/views/projects/branch_rules/_show.html.haml
@@ -9,4 +9,4 @@
= _('Define rules for who can push, merge, and the required approvals for each branch.')
.settings-content.gl-pr-0
- #js-branch-rules{ data: { project_path: @project.full_path } }
+ #js-branch-rules{ data: { project_path: @project.full_path, branch_rules_path: project_settings_repository_branch_rules_path(@project) } }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index cd7339edd1a..31041d124e4 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -1,3 +1,5 @@
+- add_page_specific_style 'page_bundles/prometheus'
+
- page_title _("Metrics Dashboard"), @environment.name
.prometheus-container
diff --git a/app/views/projects/incidents/show.html.haml b/app/views/projects/incidents/show.html.haml
index 5043f94bd5c..7a1e7f503f8 100644
--- a/app/views/projects/incidents/show.html.haml
+++ b/app/views/projects/incidents/show.html.haml
@@ -2,6 +2,7 @@
- add_to_breadcrumbs _("Incidents"), project_incidents_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Incidents")
+- add_page_specific_style 'page_bundles/incidents'
- add_page_specific_style 'page_bundles/issues_show'
= render 'projects/issuable/show', issuable: @issue
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 06c422fc4d6..76b725d140c 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -2,6 +2,7 @@
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
+- add_page_specific_style 'page_bundles/incidents'
- add_page_specific_style 'page_bundles/issues_show'
- add_page_specific_style 'page_bundles/work_items'
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index 28b433b2514..4b549aaf1cd 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -3,12 +3,10 @@
.form-group
= f.label :auth_method, _('Authentication method'), class: 'label-bold'
- .select-wrapper
- = f.select :auth_method,
- options_for_select(auth_options, mirror.auth_method),
- {}, { class: "form-control gl-form-select select-control js-mirror-auth-type qa-authentication-method" }
- = sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
- = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
+ = f.select :auth_method,
+ options_for_select(auth_options, mirror.auth_method),
+ {}, { class: "custom-select gl-form-select js-mirror-auth-type qa-authentication-method" }
+ = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
.form-group
.well-password-auth.collapse.js-well-password-auth
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 5db898067db..2e403358e2e 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -22,6 +22,8 @@
.well-segment.qa-pipeline-badges
.icon-container
= sprite_icon('flag', css_class: 'gl-top-0!')
+ - if @pipeline.schedule?
+ = gl_badge_tag _('Scheduled'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-scheduled', title: _('This pipeline was triggered by a schedule.') }
- if @pipeline.child?
- text = sprintf(s_('Pipelines|Child pipeline (%{link_start}parent%{link_end})'), { link_start: "<a href='#{pipeline_path(@pipeline.triggered_by_pipeline)}' class='text-underline'>", link_end: "</a>"}).html_safe
= gl_badge_tag text, { variant: :info, size: :sm }, { class: 'js-pipeline-child has-tooltip', title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
diff --git a/app/views/projects/settings/branch_rules/index.html.haml b/app/views/projects/settings/branch_rules/index.html.haml
index 84c060701bb..ab692a23e44 100644
--- a/app/views/projects/settings/branch_rules/index.html.haml
+++ b/app/views/projects/settings/branch_rules/index.html.haml
@@ -1,6 +1,6 @@
- add_to_breadcrumbs _('Repository Settings'), project_settings_repository_path(@project)
- page_title s_('BranchRules|Branch rules details')
-%h3= s_('BranchRules|Branch rules details')
+%h3.gl-mb-5= s_('BranchRules|Branch rules details')
-#js-branch-rules{ data: { project_path: @project.full_path } }
+#js-branch-rules{ data: { project_path: @project.full_path, protected_branches_path: project_settings_repository_path(@project, anchor: 'js-protected-branches-settings') } }
diff --git a/config/application.rb b/config/application.rb
index 6e7bf47fd71..a4607f914a6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -277,6 +277,7 @@ module Gitlab
config.assets.precompile << "page_bundles/ide.css"
config.assets.precompile << "page_bundles/import.css"
config.assets.precompile << "page_bundles/incident_management_list.css"
+ config.assets.precompile << "page_bundles/incidents.css"
config.assets.precompile << "page_bundles/issues_analytics.css"
config.assets.precompile << "page_bundles/issues_list.css"
config.assets.precompile << "page_bundles/issues_show.css"
@@ -302,6 +303,7 @@ module Gitlab
config.assets.precompile << "page_bundles/profiles/preferences.css"
config.assets.precompile << "page_bundles/project.css"
config.assets.precompile << "page_bundles/projects_edit.css"
+ config.assets.precompile << "page_bundles/prometheus.css"
config.assets.precompile << "page_bundles/reports.css"
config.assets.precompile << "page_bundles/roadmap.css"
config.assets.precompile << "page_bundles/requirements.css"
diff --git a/config/feature_flags/development/realtime_labels.yml b/config/feature_flags/development/realtime_labels.yml
index 6072a4b044a..0c047a09a6d 100644
--- a/config/feature_flags/development/realtime_labels.yml
+++ b/config/feature_flags/development/realtime_labels.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357370
milestone: '14.10'
type: development
group: group::project management
-default_enabled: false
+default_enabled: true
diff --git a/db/docs/user_phone_number_validations.yml b/db/docs/user_phone_number_validations.yml
new file mode 100644
index 00000000000..9feacd76c5a
--- /dev/null
+++ b/db/docs/user_phone_number_validations.yml
@@ -0,0 +1,9 @@
+---
+table_name: user_phone_number_validations
+classes:
+- Users::PhoneNumberValidation
+feature_categories:
+- onboarding
+description: Stores whether the user has verified their phone number
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97762
+milestone: '15.5'
diff --git a/db/migrate/20220913043728_create_user_phone_number_validations.rb b/db/migrate/20220913043728_create_user_phone_number_validations.rb
new file mode 100644
index 00000000000..21a5b4d5519
--- /dev/null
+++ b/db/migrate/20220913043728_create_user_phone_number_validations.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class CreateUserPhoneNumberValidations < Gitlab::Database::Migration[2.0]
+ enable_lock_retries!
+
+ def up
+ create_table :user_phone_number_validations, id: false do |t|
+ t.references :user, primary_key: true, default: nil, type: :bigint, index: false,
+ foreign_key: { on_delete: :cascade }
+
+ t.datetime_with_timezone :validated_at
+ t.timestamps_with_timezone null: false
+
+ t.integer :international_dial_code, null: false, limit: 1
+ t.integer :verification_attempts, null: false, default: 0, limit: 1
+ t.integer :risk_score, null: false, default: 0, limit: 1
+
+ t.text :country, null: false, limit: 3
+ t.text :phone_number, null: false, limit: 12
+ t.text :telesign_reference_xid, limit: 255
+
+ t.index [:international_dial_code, :phone_number], name: :index_user_phone_validations_on_dial_code_phone_number
+ end
+ end
+
+ def down
+ drop_table :user_phone_number_validations
+ end
+end
diff --git a/db/migrate/20220927155407_add_column_inbound_job_token_scope_enabled_to_ci_cd_setting.rb b/db/migrate/20220927155407_add_column_inbound_job_token_scope_enabled_to_ci_cd_setting.rb
new file mode 100644
index 00000000000..811138cd2f2
--- /dev/null
+++ b/db/migrate/20220927155407_add_column_inbound_job_token_scope_enabled_to_ci_cd_setting.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddColumnInboundJobTokenScopeEnabledToCiCdSetting < Gitlab::Database::Migration[2.0]
+ enable_lock_retries!
+
+ def up
+ add_column :project_ci_cd_settings, :inbound_job_token_scope_enabled, :boolean, default: false, null: false
+ end
+
+ def down
+ remove_column :project_ci_cd_settings, :inbound_job_token_scope_enabled
+ end
+end
diff --git a/db/schema_migrations/20220913043728 b/db/schema_migrations/20220913043728
new file mode 100644
index 00000000000..80dd2b0c9a4
--- /dev/null
+++ b/db/schema_migrations/20220913043728
@@ -0,0 +1 @@
+ae25d97cf0f867578fd0308ac4b2dea82710973bb2b6d9df58531b1586462838 \ No newline at end of file
diff --git a/db/schema_migrations/20220927155407 b/db/schema_migrations/20220927155407
new file mode 100644
index 00000000000..a704ba7cb41
--- /dev/null
+++ b/db/schema_migrations/20220927155407
@@ -0,0 +1 @@
+97bdc9d31ed93897f3133459779207b3b750530b615e243a625681d433090e94 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index d45381bdbe3..e33f31226d8 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19689,7 +19689,8 @@ CREATE TABLE project_ci_cd_settings (
runner_token_expiration_interval integer,
separated_caches boolean DEFAULT true NOT NULL,
opt_in_jwt boolean DEFAULT false NOT NULL,
- allow_fork_pipelines_to_run_in_parent_project boolean DEFAULT true NOT NULL
+ allow_fork_pipelines_to_run_in_parent_project boolean DEFAULT true NOT NULL,
+ inbound_job_token_scope_enabled boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE project_ci_cd_settings_id_seq
@@ -22148,6 +22149,22 @@ CREATE SEQUENCE user_permission_export_uploads_id_seq
ALTER SEQUENCE user_permission_export_uploads_id_seq OWNED BY user_permission_export_uploads.id;
+CREATE TABLE user_phone_number_validations (
+ user_id bigint NOT NULL,
+ validated_at timestamp with time zone,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ international_dial_code smallint NOT NULL,
+ verification_attempts smallint DEFAULT 0 NOT NULL,
+ risk_score smallint DEFAULT 0 NOT NULL,
+ country text NOT NULL,
+ phone_number text NOT NULL,
+ telesign_reference_xid text,
+ CONSTRAINT check_193736da9f CHECK ((char_length(country) <= 3)),
+ CONSTRAINT check_d2f31fc815 CHECK ((char_length(phone_number) <= 12)),
+ CONSTRAINT check_d7af4d3eb5 CHECK ((char_length(telesign_reference_xid) <= 255))
+);
+
CREATE TABLE user_preferences (
id integer NOT NULL,
user_id integer NOT NULL,
@@ -26476,6 +26493,9 @@ ALTER TABLE ONLY user_namespace_callouts
ALTER TABLE ONLY user_permission_export_uploads
ADD CONSTRAINT user_permission_export_uploads_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY user_phone_number_validations
+ ADD CONSTRAINT user_phone_number_validations_pkey PRIMARY KEY (user_id);
+
ALTER TABLE ONLY user_preferences
ADD CONSTRAINT user_preferences_pkey PRIMARY KEY (id);
@@ -30565,6 +30585,8 @@ CREATE INDEX index_user_namespace_callouts_on_namespace_id ON user_namespace_cal
CREATE INDEX index_user_permission_export_uploads_on_user_id_and_status ON user_permission_export_uploads USING btree (user_id, status);
+CREATE INDEX index_user_phone_validations_on_dial_code_phone_number ON user_phone_number_validations USING btree (international_dial_code, phone_number);
+
CREATE INDEX index_user_preferences_on_gitpod_enabled ON user_preferences USING btree (gitpod_enabled);
CREATE UNIQUE INDEX index_user_preferences_on_user_id ON user_preferences USING btree (user_id);
@@ -34368,6 +34390,9 @@ ALTER TABLE ONLY clusters
ALTER TABLE ONLY packages_composer_metadata
ADD CONSTRAINT fk_rails_ad48c2e5bb FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
+ALTER TABLE ONLY user_phone_number_validations
+ ADD CONSTRAINT fk_rails_ad6686f3d8 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY analytics_cycle_analytics_group_stages
ADD CONSTRAINT fk_rails_ae5da3409b FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
diff --git a/doc/administration/object_storage.md b/doc/administration/object_storage.md
index 33efa773798..c2e27375221 100644
--- a/doc/administration/object_storage.md
+++ b/doc/administration/object_storage.md
@@ -277,10 +277,13 @@ Here are the valid connection parameters for GCS:
|------------------------------|-------------------|---------|
| `provider` | Provider name. | `Google` |
| `google_project` | GCP project name. | `gcp-project-12345` |
-| `google_client_email` | Email address of the service account. | `foo@gcp-project-12345.iam.gserviceaccount.com` |
| `google_json_key_location` | JSON key path. | `/path/to/gcp-project-12345-abcde.json` |
+| `google_json_key_string` | JSON key string. | `{ "type": "service_account", "project_id": "example-project-382839", ... }` |
| `google_application_default` | Set to `true` to use [Google Cloud Application Default Credentials](https://cloud.google.com/docs/authentication/production#automatically) to locate service account credentials. | |
+GitLab reads the value of `google_json_key_location`, then `google_json_key_string`, and finally, `google_application_default`.
+It uses the first of these settings that has a value.
+
The service account must have permission to access the bucket. Learn more
in Google's
[Cloud Storage authentication documentation](https://cloud.google.com/storage/docs/authentication).
@@ -296,7 +299,6 @@ For Omnibus installations, this is an example of the `connection` setting:
gitlab_rails['object_store']['connection'] = {
'provider' => 'Google',
'google_project' => '<GOOGLE PROJECT>',
- 'google_client_email' => '<GOOGLE CLIENT EMAIL>',
'google_json_key_location' => '<FILENAME>'
}
```
diff --git a/doc/development/contributing/community_roles.md b/doc/development/contributing/community_roles.md
index 268a6f90436..8aa219d72a1 100644
--- a/doc/development/contributing/community_roles.md
+++ b/doc/development/contributing/community_roles.md
@@ -12,7 +12,7 @@ GitLab community members and their privileges/responsibilities.
|-------|------------------|--------------|
| Maintainer | Accepts merge requests on several GitLab projects | Added to the [team page](https://about.gitlab.com/company/team/). An expert on code reviews and knows the product/codebase |
| Reviewer | Performs code reviews on MRs | Added to the [team page](https://about.gitlab.com/company/team/) |
-| Developer |Has access to GitLab internal infrastructure & issues (for example, HR-related) | GitLab employee or a Core Team member (with an NDA) |
+| Developer | Has access to GitLab internal infrastructure & issues (for example, HR-related) | GitLab employee or a Core Team member (with an NDA) |
| Contributor | Can make contributions to all GitLab public projects | Have a GitLab.com account |
-[List of current reviewers/maintainers](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce).
+[List of current reviewers/maintainers](https://about.gitlab.com/handbook/engineering/projects/#gitlab).
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 00903c55089..cbccd832d78 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -26,11 +26,6 @@ Throughout this guide you will see references to CE and EE for abbreviation.
To get an overview of GitLab community membership, including those that would review or merge
your contributions, visit [the community roles page](community_roles.md).
-If you want to know how the GitLab [core team](https://about.gitlab.com/community/core-team/)
-operates, see [the GitLab contributing process](https://gitlab.com/gitlab-org/gitlab/-/blob/master/PROCESS.md).
-
-GitLab Inc engineers should refer to the [engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/).
-
## Security vulnerability disclosure
Report suspected security vulnerabilities by following the
diff --git a/doc/development/development_processes.md b/doc/development/development_processes.md
index 53b129f7d6e..10818b749ab 100644
--- a/doc/development/development_processes.md
+++ b/doc/development/development_processes.md
@@ -22,7 +22,7 @@ Must-reads:
Complementary reads:
-- [GitLab core team & GitLab Inc. contribution process](https://gitlab.com/gitlab-org/gitlab/-/blob/master/PROCESS.md)
+- [Contribute to GitLab](contributing/index.md)
- [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer)
- [Patch release process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/patch/process.md#process-for-developers)
- [Guidelines for implementing Enterprise Edition features](ee_features.md)
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 26e2a09c0e2..c3b2c178d2b 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -175,6 +175,14 @@ git checkout origin/master db/structure.sql
VERSION=<migration ID> bundle exec rails db:migrate:main
```
+### Adding new tables to GitLab Schema
+
+GitLab connects to two different Postgres databases: `main` and `ci`. New tables should be defined in [`lib/gitlab/database/gitlab_schemas.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/gitlab_schemas.yml) with the databases they need to be added to.
+
+ ```yaml
+ <TABLE_NAME>: :gitlab_main
+ ```
+
## Avoiding downtime
The document ["Avoiding downtime in migrations"](database/avoiding_downtime_in_migrations.md) specifies
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index 70ebaa68bae..2bfc7f0243a 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -107,8 +107,7 @@ In some cases, however, we may need to backport *a bug fix* to more than one sta
release, depending on the severity of the bug.
The decision on whether backporting a change is performed is done at the discretion of the
-[current release managers](https://about.gitlab.com/community/release-managers/), similar to what is
-described in the [managing bugs](https://gitlab.com/gitlab-org/gitlab/-/blob/master/PROCESS.md#managing-bugs) process,
+[current release managers](https://about.gitlab.com/community/release-managers/),
based on *all* of the following:
1. Estimated [severity](../development/contributing/issue_workflow.md#severity-labels) of the bug:
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 1f183431d3d..f55fe60d53b 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -1038,7 +1038,7 @@ ensure that it can reach your private repository. Here is an example configurati
1. Fetch the certificate from your repository URL and add it to the project:
```shell
- echo -n | openssl s_client -connect pypi.example.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > internal.crt
+ printf "\n" | openssl s_client -connect pypi.example.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > internal.crt
```
1. Point `setup.py` at the newly downloaded certificate:
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 217fb15990f..826e0b21ea7 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -448,10 +448,10 @@ To learn what happens when you sort by priority or label priority, see
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241538) in GitLab 14.10 with a [feature flag](../../administration/feature_flags.md) named `realtime_labels`, disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357370#note_991987201) in GitLab 15.1.
+> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/357370) in GitLab 15.5.
FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available, ask an
-administrator to [enable the feature flag](../../administration/feature_flags.md) named `realtime_labels`.
+On self-managed GitLab, to prevent updating labels in real-time, you can ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `realtime_labels`.
On GitLab.com, this feature is available.
Changed labels are immediately visible to other users, without refreshing the page, on the following:
diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb
index 90edc7010f4..b652d8d89cf 100644
--- a/lib/banzai/pipeline/ascii_doc_pipeline.rb
+++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb
@@ -13,6 +13,7 @@ module Banzai
Filter::ColorFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
+ Filter::WikiLinkFilter,
Filter::AsciiDocPostProcessingFilter
]
end
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index e92da3fdd20..8b62dfda677 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -547,6 +547,7 @@ user_group_callouts: :gitlab_main
user_project_callouts: :gitlab_main
user_highest_roles: :gitlab_main
user_interacted_projects: :gitlab_main
+user_phone_number_validations: :gitlab_main
user_permission_export_uploads: :gitlab_main
user_preferences: :gitlab_main
users: :gitlab_main
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index 2228fcb886e..cbe6ba7721a 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -87,10 +87,6 @@ module Gitlab
end
end
- def count_page_versions(page_path)
- @repository.count_commits(ref: 'HEAD', path: page_path)
- end
-
def preview_slug(title, format)
GollumSlug.generate(title, format)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 604d065267f..cbd9020b768 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6766,39 +6766,78 @@ msgstr ""
msgid "Branch rules"
msgstr ""
+msgid "BranchRules|%{linkStart}Wildcards%{linkEnd} such as *-stable or production/ are supported"
+msgstr ""
+
msgid "BranchRules|%{linkStart}Wildcards%{linkEnd} such as *-stable or production/* are supported."
msgstr ""
+msgid "BranchRules|All branches"
+msgstr ""
+
+msgid "BranchRules|All users with push access are allowed to force push."
+msgstr ""
+
msgid "BranchRules|Allow all users with push access to %{linkStart}force push%{linkEnd}."
msgstr ""
msgid "BranchRules|Allowed to merge"
msgstr ""
+msgid "BranchRules|Allowed to merge (%{total})"
+msgstr ""
+
msgid "BranchRules|Allowed to push"
msgstr ""
+msgid "BranchRules|Allowed to push (%{total})"
+msgstr ""
+
msgid "BranchRules|An error occurred while fetching branches."
msgstr ""
+msgid "BranchRules|Approvals"
+msgstr ""
+
msgid "BranchRules|Branch"
msgstr ""
+msgid "BranchRules|Branch name or pattern"
+msgstr ""
+
msgid "BranchRules|Branch rules details"
msgstr ""
msgid "BranchRules|Create wildcard: %{searchTerm}"
msgstr ""
+msgid "BranchRules|Details"
+msgstr ""
+
+msgid "BranchRules|Force push"
+msgstr ""
+
+msgid "BranchRules|Force push is not allowed."
+msgstr ""
+
msgid "BranchRules|Groups"
msgstr ""
msgid "BranchRules|Keep stable branches secure and force developers to use merge requests. %{linkStart}What are protected branches?%{linkEnd}"
msgstr ""
+msgid "BranchRules|Manage in Protected Branches"
+msgstr ""
+
+msgid "BranchRules|No data to display"
+msgstr ""
+
msgid "BranchRules|No matching results"
msgstr ""
+msgid "BranchRules|Protect branch"
+msgstr ""
+
msgid "BranchRules|Protections"
msgstr ""
@@ -6811,6 +6850,9 @@ msgstr ""
msgid "BranchRules|Roles"
msgstr ""
+msgid "BranchRules|Status checks"
+msgstr ""
+
msgid "BranchRules|Target Branch"
msgstr ""
@@ -41268,6 +41310,9 @@ msgstr ""
msgid "This project will be deleted on %{date} since its parent group '%{parent_group_name}' has been scheduled for deletion."
msgstr ""
+msgid "This project's pipeline configuration is located outside this repository"
+msgstr ""
+
msgid "This release was created with a date in the past. Evidence collection at the moment of the release is unavailable."
msgstr ""
@@ -41737,6 +41782,9 @@ msgstr ""
msgid "To define internal users, first enable new users set to external"
msgstr ""
+msgid "To edit the pipeline configuration, you must go to the project or external site that hosts the file."
+msgstr ""
+
msgid "To enable Registration Features, first enable Service Ping."
msgstr ""
@@ -46769,6 +46817,9 @@ msgstr ""
msgid "cURL:"
msgstr ""
+msgid "can contain only digits"
+msgstr ""
+
msgid "can contain only letters of the Base64 alphabet (RFC4648) with the addition of '@', ':' and '.'"
msgstr ""
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 871917a725e..0ee834a5eef 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -55,6 +55,7 @@ FactoryBot.define do
forward_deployment_enabled { nil }
restrict_user_defined_variables { nil }
ci_job_token_scope_enabled { nil }
+ ci_inbound_job_token_scope_enabled { nil }
runner_token_expiration_interval { nil }
runner_token_expiration_interval_human_readable { nil }
end
@@ -113,6 +114,7 @@ FactoryBot.define do
project.keep_latest_artifact = evaluator.keep_latest_artifact unless evaluator.keep_latest_artifact.nil?
project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil?
project.ci_job_token_scope_enabled = evaluator.ci_job_token_scope_enabled unless evaluator.ci_job_token_scope_enabled.nil?
+ project.ci_inbound_job_token_scope_enabled = evaluator.ci_inbound_job_token_scope_enabled unless evaluator.ci_inbound_job_token_scope_enabled.nil?
project.runner_token_expiration_interval = evaluator.runner_token_expiration_interval unless evaluator.runner_token_expiration_interval.nil?
project.runner_token_expiration_interval_human_readable = evaluator.runner_token_expiration_interval_human_readable unless evaluator.runner_token_expiration_interval_human_readable.nil?
diff --git a/spec/factories/users/phone_number_validations.rb b/spec/factories/users/phone_number_validations.rb
new file mode 100644
index 00000000000..da53dda89b4
--- /dev/null
+++ b/spec/factories/users/phone_number_validations.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :phone_number_validation, class: 'Users::PhoneNumberValidation' do
+ user
+ country { 'US' }
+ international_dial_code { 1 }
+ phone_number { '555' }
+ end
+end
diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
index fbb5c24f6e1..db2b3fc2f4b 100644
--- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'Projects > Wiki > User views wiki in project page' do
end
context 'when repository is disabled for project' do
- let_it_be(:project) do
+ let(:project) do
create(:project,
:wiki_repo,
:repository_disabled,
@@ -17,16 +17,31 @@ RSpec.describe 'Projects > Wiki > User views wiki in project page' do
end
context 'when wiki homepage contains a link' do
- before do
- create(:wiki_page, wiki: project.wiki, title: 'home', content: '[some link](other-page)')
+ shared_examples 'wiki homepage contains a link' do
+ it 'displays the correct URL for the link' do
+ visit project_path(project)
+ expect(page).to have_link(
+ 'some link',
+ href: project_wiki_path(project, 'other-page')
+ )
+ end
end
- it 'displays the correct URL for the link' do
- visit project_path(project)
- expect(page).to have_link(
- 'some link',
- href: project_wiki_path(project, 'other-page')
- )
+ context 'when using markdown' do
+ before do
+ create(:wiki_page, wiki: project.wiki, title: 'home', content: '[some link](other-page)')
+ end
+
+ it_behaves_like 'wiki homepage contains a link'
+ end
+
+ context 'when using asciidoc' do
+ before do
+ create(:wiki_page, wiki: project.wiki, title: 'home', content: 'link:other-page[some link]',
+ format: :asciidoc)
+ end
+
+ it_behaves_like 'wiki homepage contains a link'
end
end
end
diff --git a/spec/frontend/access_tokens/components/access_token_table_app_spec.js b/spec/frontend/access_tokens/components/access_token_table_app_spec.js
index a460d18cc60..0917544b40d 100644
--- a/spec/frontend/access_tokens/components/access_token_table_app_spec.js
+++ b/spec/frontend/access_tokens/components/access_token_table_app_spec.js
@@ -199,16 +199,39 @@ describe('~/access_tokens/components/access_token_table_app', () => {
expect(button.props('category')).toBe('tertiary');
});
- describe('revoke path', () => {
+ describe('when revoke_path is', () => {
beforeEach(() => {
createComponent({ showRole: true });
});
+ describe('absent in all tokens', () => {
+ it('should not include `Action` column', async () => {
+ await triggerSuccess(defaultActiveAccessTokens.map(({ revoke_path, ...rest }) => rest));
+
+ const headers = findHeaders();
+ expect(headers).toHaveLength(6);
+ [
+ __('Token name'),
+ __('Scopes'),
+ s__('AccessTokens|Created'),
+ __('Last Used'),
+ __('Expires'),
+ __('Role'),
+ ].forEach((text, index) => {
+ expect(headers.at(index).text()).toBe(text);
+ });
+ });
+ });
+
it.each([{ revoke_path: null }, { revoke_path: undefined }])(
- 'with %p, does not show revoke button',
+ '%p in some tokens, does not show revoke button',
async (input) => {
- await triggerSuccess(defaultActiveAccessTokens.map((data) => ({ ...data, ...input })));
+ await triggerSuccess([
+ defaultActiveAccessTokens.map((data) => ({ ...data, ...input }))[0],
+ defaultActiveAccessTokens[1],
+ ]);
+ expect(findHeaders().at(6).text()).toBe(__('Action'));
expect(findCells().at(6).findComponent(GlButton).exists()).toBe(false);
},
);
diff --git a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js
index 8e0a73b6e7c..c76c3460e99 100644
--- a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js
+++ b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js
@@ -7,6 +7,7 @@ describe('Pipeline editor empty state', () => {
let wrapper;
const defaultProvide = {
emptyStateIllustrationPath: 'my/svg/path',
+ usesExternalConfig: false,
};
const createComponent = ({ provide } = {}) => {
@@ -18,6 +19,7 @@ describe('Pipeline editor empty state', () => {
const findFileNav = () => wrapper.findComponent(PipelineEditorFileNav);
const findSvgImage = () => wrapper.find('img');
const findTitle = () => wrapper.find('h1');
+ const findExternalCiInstructions = () => wrapper.find('p');
const findConfirmButton = () => wrapper.findComponent(GlButton);
const findDescription = () => wrapper.findComponent(GlSprintf);
@@ -25,7 +27,33 @@ describe('Pipeline editor empty state', () => {
wrapper.destroy();
});
- describe('template', () => {
+ describe('when project uses an external CI config', () => {
+ beforeEach(() => {
+ createComponent({
+ provide: { usesExternalConfig: true },
+ });
+ });
+
+ it('renders an svg image', () => {
+ expect(findSvgImage().exists()).toBe(true);
+ });
+
+ it('renders the correct title and instructions', () => {
+ expect(findTitle().exists()).toBe(true);
+ expect(findExternalCiInstructions().exists()).toBe(true);
+
+ expect(findExternalCiInstructions().html()).toContain(
+ wrapper.vm.$options.i18n.externalCiInstructions,
+ );
+ expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.externalCiNote);
+ });
+
+ it('does not render the CTA button', () => {
+ expect(findConfirmButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when project uses an accessible CI config', () => {
beforeEach(() => {
createComponent();
});
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index 1989f23a415..9fe1536d3f5 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -55,11 +55,12 @@ jest.mock('~/lib/utils/url_utility', () => ({
const localVue = createLocalVue();
localVue.use(VueApollo);
-const mockProvide = {
+const defaultProvide = {
ciConfigPath: mockCiConfigPath,
defaultBranch: mockDefaultBranch,
newMergeRequestPath: mockNewMergeRequestPath,
projectFullPath: mockProjectFullPath,
+ usesExternalConfig: false,
};
describe('Pipeline editor app component', () => {
@@ -79,7 +80,7 @@ describe('Pipeline editor app component', () => {
stubs = {},
} = {}) => {
wrapper = shallowMount(PipelineEditorApp, {
- provide: { ...mockProvide, ...provide },
+ provide: { ...defaultProvide, ...provide },
stubs,
mocks: {
$apollo: {
@@ -229,6 +230,22 @@ describe('Pipeline editor app component', () => {
mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults);
});
+ describe('when project uses an external CI config file', () => {
+ beforeEach(async () => {
+ await createComponentWithApollo({
+ provide: {
+ usesExternalConfig: true,
+ },
+ });
+ });
+
+ it('shows an empty state and does not show editor home component', () => {
+ expect(findEmptyState().exists()).toBe(true);
+ expect(findAlert().exists()).toBe(false);
+ expect(findEditorHome().exists()).toBe(false);
+ });
+ });
+
describe('when file exists', () => {
beforeEach(async () => {
await createComponentWithApollo();
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
index d155c7f9246..3176b28d547 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
@@ -1,17 +1,51 @@
-import { getParameterByName } from '~/lib/utils/url_utility';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import * as util from '~/lib/utils/url_utility';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import RuleView from '~/projects/settings/branch_rules/components/view/index.vue';
+import {
+ I18N,
+ ALL_BRANCHES_WILDCARD,
+} from '~/projects/settings/branch_rules/components/view/constants';
+import Protection from '~/projects/settings/branch_rules/components/view/protection.vue';
+import branchRulesQuery from '~/projects/settings/branch_rules/queries/branch_rules_details.query.graphql';
+import { sprintf } from '~/locale';
+import { branchProtectionsMockResponse } from './mock_data';
jest.mock('~/lib/utils/url_utility', () => ({
- getParameterByName: jest.fn().mockImplementation(() => 'main'),
+ getParameterByName: jest.fn().mockReturnValue('main'),
+ joinPaths: jest.fn(),
}));
+Vue.use(VueApollo);
+
+const protectionMockProps = {
+ headerLinkHref: 'protected/branches',
+ headerLinkTitle: 'Manage in Protected Branches',
+ roles: [{ accessLevelDescription: 'Maintainers' }],
+ users: [{ avatarUrl: 'test.com/user.png', name: 'peter', webUrl: 'test.com' }],
+};
+
describe('View branch rules', () => {
let wrapper;
+ let fakeApollo;
const projectPath = 'test/testing';
+ const protectedBranchesPath = 'protected/branches';
+ const branchProtectionsMockRequestHandler = jest
+ .fn()
+ .mockResolvedValue(branchProtectionsMockResponse);
+
+ const createComponent = async () => {
+ fakeApollo = createMockApollo([[branchRulesQuery, branchProtectionsMockRequestHandler]]);
- const createComponent = () => {
- wrapper = shallowMountExtended(RuleView, { propsData: { projectPath } });
+ wrapper = shallowMountExtended(RuleView, {
+ apolloProvider: fakeApollo,
+ provide: { projectPath, protectedBranchesPath },
+ });
+
+ await waitForPromises();
};
beforeEach(() => createComponent());
@@ -19,9 +53,49 @@ describe('View branch rules', () => {
afterEach(() => wrapper.destroy());
const findBranchName = () => wrapper.findByTestId('branch');
+ const findBranchTitle = () => wrapper.findByTestId('branch-title');
+ const findBranchProtectionTitle = () => wrapper.findByText(I18N.protectBranchTitle);
+ const findBranchProtections = () => wrapper.findAllComponents(Protection);
+ const findForcePushTitle = () => wrapper.findByText(I18N.allowForcePushDescription);
it('gets the branch param from url and renders it in the view', () => {
- expect(getParameterByName).toHaveBeenCalledWith('branch');
+ expect(util.getParameterByName).toHaveBeenCalledWith('branch');
expect(findBranchName().text()).toBe('main');
+ expect(findBranchTitle().text()).toBe(I18N.branchNameOrPattern);
+ });
+
+ it('renders the correct label if all branches are targeted', async () => {
+ jest.spyOn(util, 'getParameterByName').mockReturnValueOnce(ALL_BRANCHES_WILDCARD);
+ await createComponent();
+
+ expect(findBranchName().text()).toBe(I18N.allBranches);
+ expect(findBranchTitle().text()).toBe(I18N.targetBranch);
+ jest.restoreAllMocks();
+ });
+
+ it('renders the correct branch title', () => {
+ expect(findBranchTitle().exists()).toBe(true);
+ });
+
+ it('renders a branch protection title', () => {
+ expect(findBranchProtectionTitle().exists()).toBe(true);
+ });
+
+ it('renders a branch protection component for push rules', () => {
+ expect(findBranchProtections().at(0).props()).toMatchObject({
+ header: sprintf(I18N.allowedToPushHeader, { total: 2 }),
+ ...protectionMockProps,
+ });
+ });
+
+ it('renders force push protection', () => {
+ expect(findForcePushTitle().exists()).toBe(true);
+ });
+
+ it('renders a branch protection component for merge rules', () => {
+ expect(findBranchProtections().at(1).props()).toMatchObject({
+ header: sprintf(I18N.allowedToMergeHeader, { total: 2 }),
+ ...protectionMockProps,
+ });
});
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
index 155fc5342ca..c5774977205 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
@@ -52,3 +52,80 @@ export const protectionRowPropsMock = {
users: usersMock,
accessLevels: accessLevelsMock,
};
+
+export const accessLevelsMockResponse = [
+ {
+ __typename: 'PushAccessLevelEdge',
+ node: {
+ __typename: 'PushAccessLevel',
+ accessLevel: 40,
+ accessLevelDescription: 'Jona Langworth',
+ group: null,
+ user: {
+ __typename: 'UserCore',
+ id: '123',
+ webUrl: 'test.com',
+ name: 'peter',
+ avatarUrl: 'test.com/user.png',
+ },
+ },
+ },
+ {
+ __typename: 'PushAccessLevelEdge',
+ node: {
+ __typename: 'PushAccessLevel',
+ accessLevel: 40,
+ accessLevelDescription: 'Maintainers',
+ group: null,
+ user: null,
+ },
+ },
+];
+
+export const branchProtectionsMockResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/6',
+ __typename: 'Project',
+ branchRules: {
+ __typename: 'BranchRuleConnection',
+ nodes: [
+ {
+ __typename: 'BranchRule',
+ name: 'main',
+ branchProtection: {
+ __typename: 'BranchProtection',
+ allowForcePush: true,
+ codeOwnerApprovalRequired: true,
+ mergeAccessLevels: {
+ __typename: 'MergeAccessLevelConnection',
+ edges: accessLevelsMockResponse,
+ },
+ pushAccessLevels: {
+ __typename: 'PushAccessLevelConnection',
+ edges: accessLevelsMockResponse,
+ },
+ },
+ },
+ {
+ __typename: 'BranchRule',
+ name: '*',
+ branchProtection: {
+ __typename: 'BranchProtection',
+ allowForcePush: true,
+ codeOwnerApprovalRequired: true,
+ mergeAccessLevels: {
+ __typename: 'MergeAccessLevelConnection',
+ edges: [],
+ },
+ pushAccessLevels: {
+ __typename: 'PushAccessLevelConnection',
+ edges: [],
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+};
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
index a1a2016fa17..7770e1fb2aa 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
@@ -56,7 +56,9 @@ describe('Branch rule protection row', () => {
expect(findAccessLevels().at(0).text()).toBe(
protectionRowPropsMock.accessLevels[0].accessLevelDescription,
);
- expect(findAccessLevels().at(1).text()).toBe(
+ expect(findAccessLevels().at(1).text()).toContain(',');
+
+ expect(findAccessLevels().at(1).text()).toContain(
protectionRowPropsMock.accessLevels[1].accessLevelDescription,
);
});
diff --git a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
index 117e106e15b..4603436c40a 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
@@ -7,7 +7,7 @@ import BranchRules, { i18n } from '~/projects/settings/repository/branch_rules/a
import BranchRule from '~/projects/settings/repository/branch_rules/components/branch_rule.vue';
import branchRulesQuery from '~/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
import { createAlert } from '~/flash';
-import { branchRulesMockResponse, propsDataMock } from './mock_data';
+import { branchRulesMockResponse, appProvideMock } from './mock_data';
jest.mock('~/flash');
@@ -24,9 +24,7 @@ describe('Branch rules app', () => {
wrapper = mountExtended(BranchRules, {
apolloProvider: fakeApollo,
- propsData: {
- ...propsDataMock,
- },
+ provide: appProvideMock,
});
await waitForPromises();
@@ -49,7 +47,11 @@ describe('Branch rules app', () => {
it('renders branch rules', () => {
const { nodes } = branchRulesMockResponse.data.project.branchRules;
- expect(findAllBranchRules().at(0).text()).toBe(nodes[0].name);
- expect(findAllBranchRules().at(1).text()).toBe(nodes[1].name);
+
+ expect(findAllBranchRules().length).toBe(nodes.length);
+
+ expect(findAllBranchRules().at(0).props('name')).toBe(nodes[0].name);
+
+ expect(findAllBranchRules().at(1).props('name')).toBe(nodes[1].name);
});
});
diff --git a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
index 924dab60704..2bc705f538b 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
@@ -2,26 +2,24 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import BranchRule, {
i18n,
} from '~/projects/settings/repository/branch_rules/components/branch_rule.vue';
-
-const defaultProps = {
- name: 'main',
- isDefault: true,
- isProtected: true,
- approvalDetails: ['requires approval from TEST', '2 status checks'],
-};
+import { branchRuleProvideMock, branchRulePropsMock } from '../mock_data';
describe('Branch rule', () => {
let wrapper;
const createComponent = (props = {}) => {
- wrapper = shallowMountExtended(BranchRule, { propsData: { ...defaultProps, ...props } });
+ wrapper = shallowMountExtended(BranchRule, {
+ provide: branchRuleProvideMock,
+ propsData: { ...branchRulePropsMock, ...props },
+ });
};
const findDefaultBadge = () => wrapper.findByText(i18n.defaultLabel);
const findProtectedBadge = () => wrapper.findByText(i18n.protectedLabel);
- const findBranchName = () => wrapper.findByText(defaultProps.name);
+ const findBranchName = () => wrapper.findByText(branchRulePropsMock.name);
const findProtectionDetailsList = () => wrapper.findByRole('list');
const findProtectionDetailsListItems = () => wrapper.findAllByRole('listitem');
+ const findDetailsButton = () => wrapper.findByText(i18n.detailsButtonLabel);
beforeEach(() => createComponent());
@@ -52,7 +50,17 @@ describe('Branch rule', () => {
});
it('renders the protection details list items', () => {
- expect(findProtectionDetailsListItems().at(0).text()).toBe(defaultProps.approvalDetails[0]);
- expect(findProtectionDetailsListItems().at(1).text()).toBe(defaultProps.approvalDetails[1]);
+ expect(findProtectionDetailsListItems().at(0).text()).toBe(
+ branchRulePropsMock.approvalDetails[0],
+ );
+ expect(findProtectionDetailsListItems().at(1).text()).toBe(
+ branchRulePropsMock.approvalDetails[1],
+ );
+ });
+
+ it('renders a detail button with the correct href', () => {
+ expect(findDetailsButton().attributes('href')).toBe(
+ `${branchRuleProvideMock.branchRulesPath}?branch=${branchRulePropsMock.name}`,
+ );
});
});
diff --git a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
index 14ed35f047d..bac82992c4d 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
@@ -20,6 +20,17 @@ export const branchRulesMockResponse = {
},
};
-export const propsDataMock = {
+export const appProvideMock = {
projectPath: 'some/project/path',
};
+
+export const branchRuleProvideMock = {
+ branchRulesPath: 'settings/repository/branch_rules',
+};
+
+export const branchRulePropsMock = {
+ name: 'main',
+ isDefault: true,
+ isProtected: true,
+ approvalDetails: ['requires approval from TEST', '2 status checks'],
+};
diff --git a/spec/helpers/ci/pipeline_editor_helper_spec.rb b/spec/helpers/ci/pipeline_editor_helper_spec.rb
index 1950d685980..95d90ff5dde 100644
--- a/spec/helpers/ci/pipeline_editor_helper_spec.rb
+++ b/spec/helpers/ci/pipeline_editor_helper_spec.rb
@@ -25,6 +25,30 @@ RSpec.describe Ci::PipelineEditorHelper do
describe '#js_pipeline_editor_data' do
let(:project) { create(:project, :repository) }
+ let(:default_helper_data) do
+ {
+ "ci-config-path": project.ci_config_path_or_default,
+ "ci-examples-help-page-path" => help_page_path('ci/examples/index'),
+ "ci-help-page-path" => help_page_path('ci/index'),
+ "ci-lint-path" => project_ci_lint_path(project),
+ "default-branch" => project.default_branch_or_main,
+ "empty-state-illustration-path" => 'illustrations/empty.svg',
+ "initial-branch-name" => nil,
+ "includes-help-page-path" => help_page_path('ci/yaml/includes'),
+ "lint-help-page-path" => help_page_path('ci/lint', anchor: 'check-cicd-syntax'),
+ "lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available-message'),
+ "needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'),
+ "new-merge-request-path" => '/mock/project/-/merge_requests/new',
+ "pipeline-page-path" => project_pipelines_path(project),
+ "project-path" => project.path,
+ "project-full-path" => project.full_path,
+ "project-namespace" => project.namespace.full_path,
+ "simulate-pipeline-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'simulate-a-cicd-pipeline'),
+ "uses-external-config" => 'false',
+ "validate-tab-illustration-path" => 'illustrations/validate.svg',
+ "yml-help-page-path" => help_page_path('ci/yaml/index')
+ }
+ end
before do
allow(helper)
@@ -46,29 +70,10 @@ RSpec.describe Ci::PipelineEditorHelper do
context 'with a project with commits' do
it 'returns pipeline editor data' do
- expect(pipeline_editor_data).to eq({
- "ci-config-path": project.ci_config_path_or_default,
- "ci-examples-help-page-path" => help_page_path('ci/examples/index'),
- "ci-help-page-path" => help_page_path('ci/index'),
- "ci-lint-path" => project_ci_lint_path(project),
- "default-branch" => project.default_branch_or_main,
- "empty-state-illustration-path" => 'illustrations/empty.svg',
- "initial-branch-name" => nil,
- "includes-help-page-path" => help_page_path('ci/yaml/includes'),
- "lint-help-page-path" => help_page_path('ci/lint', anchor: 'check-cicd-syntax'),
- "lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available-message'),
- "needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'),
- "new-merge-request-path" => '/mock/project/-/merge_requests/new',
+ expect(pipeline_editor_data).to eq(default_helper_data.merge({
"pipeline_etag" => graphql_etag_pipeline_sha_path(project.commit.sha),
- "pipeline-page-path" => project_pipelines_path(project),
- "project-path" => project.path,
- "project-full-path" => project.full_path,
- "project-namespace" => project.namespace.full_path,
- "simulate-pipeline-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'simulate-a-cicd-pipeline'),
- "total-branches" => project.repository.branches.length,
- "validate-tab-illustration-path" => 'illustrations/validate.svg',
- "yml-help-page-path" => help_page_path('ci/yaml/index')
- })
+ "total-branches" => project.repository.branches.length
+ }))
end
end
@@ -76,29 +81,10 @@ RSpec.describe Ci::PipelineEditorHelper do
let(:project) { create(:project, :empty_repo) }
it 'returns pipeline editor data' do
- expect(pipeline_editor_data).to eq({
- "ci-config-path": project.ci_config_path_or_default,
- "ci-examples-help-page-path" => help_page_path('ci/examples/index'),
- "ci-help-page-path" => help_page_path('ci/index'),
- "ci-lint-path" => project_ci_lint_path(project),
- "default-branch" => project.default_branch_or_main,
- "empty-state-illustration-path" => 'illustrations/empty.svg',
- "initial-branch-name" => nil,
- "includes-help-page-path" => help_page_path('ci/yaml/includes'),
- "lint-help-page-path" => help_page_path('ci/lint', anchor: 'check-cicd-syntax'),
- "lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available-message'),
- "needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'),
- "new-merge-request-path" => '/mock/project/-/merge_requests/new',
+ expect(pipeline_editor_data).to eq(default_helper_data.merge({
"pipeline_etag" => '',
- "pipeline-page-path" => project_pipelines_path(project),
- "project-path" => project.path,
- "project-full-path" => project.full_path,
- "project-namespace" => project.namespace.full_path,
- "simulate-pipeline-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'simulate-a-cicd-pipeline'),
- "total-branches" => 0,
- "validate-tab-illustration-path" => 'illustrations/validate.svg',
- "yml-help-page-path" => help_page_path('ci/yaml/index')
- })
+ "total-branches" => 0
+ }))
end
end
@@ -113,6 +99,28 @@ RSpec.describe Ci::PipelineEditorHelper do
end
end
+ context 'with a remote CI config' do
+ before do
+ create(:commit, project: project)
+ project.ci_config_path = 'http://example.com/path/to/ci/config.yml'
+ end
+
+ it 'returns true for uses-external-config in pipeline editor data' do
+ expect(pipeline_editor_data['uses-external-config']).to eq('true')
+ end
+ end
+
+ context 'with a CI config from an external project' do
+ before do
+ create(:commit, project: project)
+ project.ci_config_path = '.gitlab-ci.yml@group/project'
+ end
+
+ it 'returns true for uses-external-config in pipeline editor data' do
+ expect(pipeline_editor_data['uses-external-config']).to eq('true')
+ end
+ end
+
context 'with a non-default branch name' do
let(:user) { create(:user) }
diff --git a/spec/lib/object_storage/config_spec.rb b/spec/lib/object_storage/config_spec.rb
index 9a0e83bfd5e..2a81142ea44 100644
--- a/spec/lib/object_storage/config_spec.rb
+++ b/spec/lib/object_storage/config_spec.rb
@@ -136,7 +136,6 @@ RSpec.describe ObjectStorage::Config do
let(:credentials) do
{
provider: 'Google',
- google_client_email: 'foo@gcp-project.example.com',
google_json_key_location: '/path/to/gcp.json'
}
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 20e8245dc2e..bac76fbabca 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -912,6 +912,12 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#ci_inbound_job_token_scope_enabled?' do
+ it_behaves_like 'a ci_cd_settings predicate method', prefix: 'ci_' do
+ let(:delegated_method) { :inbound_job_token_scope_enabled? }
+ end
+ end
+
describe '#restrict_user_defined_variables?' do
it_behaves_like 'a ci_cd_settings predicate method' do
let(:delegated_method) { :restrict_user_defined_variables? }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8d848ad528d..009f5ff8e5f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -101,6 +101,7 @@ RSpec.describe User do
it { is_expected.to have_one(:atlassian_identity) }
it { is_expected.to have_one(:user_highest_role) }
it { is_expected.to have_one(:credit_card_validation) }
+ it { is_expected.to have_one(:phone_number_validation) }
it { is_expected.to have_one(:banned_user) }
it { is_expected.to have_many(:snippets).dependent(:destroy) }
it { is_expected.to have_many(:members) }
diff --git a/spec/models/users/phone_number_validation_spec.rb b/spec/models/users/phone_number_validation_spec.rb
new file mode 100644
index 00000000000..2f0fd1d3ac9
--- /dev/null
+++ b/spec/models/users/phone_number_validation_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::PhoneNumberValidation do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:banned_user) }
+
+ it { is_expected.to validate_presence_of(:country) }
+ it { is_expected.to validate_length_of(:country).is_at_most(3) }
+
+ it { is_expected.to validate_presence_of(:international_dial_code) }
+
+ it {
+ is_expected.to validate_numericality_of(:international_dial_code)
+ .only_integer
+ .is_greater_than_or_equal_to(1)
+ .is_less_than_or_equal_to(999)
+ }
+
+ it { is_expected.to validate_presence_of(:phone_number) }
+ it { is_expected.to validate_length_of(:phone_number).is_at_most(12) }
+ it { is_expected.to allow_value('555555').for(:phone_number) }
+ it { is_expected.not_to allow_value('555-555').for(:phone_number) }
+ it { is_expected.not_to allow_value('+555555').for(:phone_number) }
+ it { is_expected.not_to allow_value('555 555').for(:phone_number) }
+
+ it { is_expected.to validate_length_of(:telesign_reference_xid).is_at_most(255) }
+
+ describe '.related_to_banned_user?' do
+ let_it_be(:international_dial_code) { 1 }
+ let_it_be(:phone_number) { '555' }
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:banned_user) { create(:user, :banned) }
+
+ subject(:related_to_banned_user?) do
+ described_class.related_to_banned_user?(international_dial_code, phone_number)
+ end
+
+ context 'when banned user has the same international dial code and phone number' do
+ before do
+ create(:phone_number_validation, user: banned_user)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when banned user has the same international dial code and phone number, but different country code' do
+ before do
+ create(:phone_number_validation, user: banned_user, country: 'CA')
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when banned user does not have the same international dial code' do
+ before do
+ create(:phone_number_validation, user: banned_user, international_dial_code: 61)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when banned user does not have the same phone number' do
+ before do
+ create(:phone_number_validation, user: banned_user, phone_number: '666')
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when not-banned user has the same international dial code and phone number' do
+ before do
+ create(:phone_number_validation, user: user)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 96c396f085c..fcb041aebe5 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -3,27 +3,24 @@
require "spec_helper"
RSpec.describe WikiPage do
- let_it_be(:user) { create(:user) }
- let_it_be(:container) { create(:project) }
+ let(:user) { create(:user) }
+ let(:container) { create(:project) }
+ let(:wiki) { container.wiki }
- def create_wiki_page(attrs = {})
- page = build_wiki_page(attrs)
+ def create_wiki_page(container, attrs = {})
+ page = build_wiki_page(container, attrs)
page.create(message: (attrs[:message] || 'test commit'))
container.wiki.find_page(page.slug)
end
- def build_wiki_page(attrs = {})
+ def build_wiki_page(container, attrs = {})
wiki_page_attrs = { container: container, content: 'test content' }.merge(attrs)
build(:wiki_page, wiki_page_attrs)
end
- def wiki
- container.wiki
- end
-
def disable_front_matter
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => false)
end
@@ -32,11 +29,20 @@ RSpec.describe WikiPage do
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => thing)
end
+ def force_wiki_change_branch
+ old_default_branch = wiki.default_branch
+ wiki.repository.add_branch(user, 'another_branch', old_default_branch)
+ wiki.repository.rm_branch(user, old_default_branch)
+ wiki.repository.expire_status_cache
+
+ wiki.container.clear_memoization(:wiki)
+ end
+
# Use for groups of tests that do not modify their `subject`.
#
# include_context 'subject is persisted page', title: 'my title'
shared_context 'subject is persisted page' do |attrs = {}|
- let_it_be(:persisted_page) { create_wiki_page(attrs) }
+ let(:persisted_page) { create_wiki_page(container, attrs) }
subject { persisted_page }
end
@@ -192,7 +198,7 @@ RSpec.describe WikiPage do
end
describe "validations" do
- subject { build_wiki_page }
+ subject { build_wiki_page(container) }
it "validates presence of title" do
subject.attributes.delete(:title)
@@ -357,7 +363,7 @@ RSpec.describe WikiPage do
let(:title) { attributes[:title] }
- subject { build_wiki_page }
+ subject { build_wiki_page(container) }
context "with valid attributes" do
it "saves the wiki page" do
@@ -394,7 +400,7 @@ RSpec.describe WikiPage do
let(:title) { 'Index v1.2.3' }
describe "#create" do
- subject { build_wiki_page }
+ subject { build_wiki_page(container) }
it "saves the wiki page and returns true", :aggregate_failures do
attributes = { title: title, content: "Home Page", format: "markdown" }
@@ -405,7 +411,7 @@ RSpec.describe WikiPage do
end
describe '#update' do
- subject { create_wiki_page(title: title) }
+ subject { create_wiki_page(container, title: title) }
it 'updates the content of the page and returns true', :aggregate_failures do
expect(subject.update(content: 'new content')).to be_truthy
@@ -420,7 +426,7 @@ RSpec.describe WikiPage do
describe "#update" do
let!(:original_title) { subject.title }
- subject { create_wiki_page }
+ subject { create_wiki_page(container) }
context "with valid attributes" do
it "updates the content of the page" do
@@ -527,7 +533,7 @@ RSpec.describe WikiPage do
describe 'in subdir' do
it 'keeps the page in the same dir when the content is updated' do
title = 'foo/Existing Page'
- page = create_wiki_page(title: title)
+ page = create_wiki_page(container, title: title)
expect(page.slug).to eq 'foo/Existing-Page'
expect(page.update(title: title, content: 'new_content')).to be_truthy
@@ -541,7 +547,7 @@ RSpec.describe WikiPage do
context 'when renaming a page' do
it 'raises an error if the page already exists' do
- existing_page = create_wiki_page
+ existing_page = create_wiki_page(container)
expect { subject.update(title: existing_page.title, content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
expect(subject.title).to eq original_title
@@ -584,7 +590,7 @@ RSpec.describe WikiPage do
describe 'in subdir' do
it 'moves the page to the root folder if the title is preceded by /' do
- page = create_wiki_page(title: 'foo/Existing Page')
+ page = create_wiki_page(container, title: 'foo/Existing Page')
expect(page.slug).to eq 'foo/Existing-Page'
expect(page.update(title: '/Existing Page', content: 'new_content')).to be_truthy
@@ -592,7 +598,7 @@ RSpec.describe WikiPage do
end
it 'does nothing if it has the same title' do
- page = create_wiki_page(title: 'foo/Another Existing Page')
+ page = create_wiki_page(container, title: 'foo/Another Existing Page')
original_path = page.slug
@@ -625,7 +631,7 @@ RSpec.describe WikiPage do
describe "#delete" do
it "deletes the page and returns true", :aggregate_failures do
- page = create_wiki_page
+ page = create_wiki_page(container)
expect do
expect(page.delete).to eq(true)
@@ -634,22 +640,88 @@ RSpec.describe WikiPage do
end
describe "#versions" do
- let(:subject) { create_wiki_page }
+ let(:subject) { create_wiki_page(container) }
+
+ before do
+ 3.times { |i| subject.update(content: "content #{i}") }
+ end
+
+ context 'number of versions is less than the default paginiated per page' do
+ it "returns an array of all commits for the page" do
+ expect(subject.versions).to be_a(::CommitCollection)
+ expect(subject.versions.length).to eq(4)
+ expect(subject.versions.first.id).to eql(subject.last_version.id)
+ end
+ end
+
+ context 'number of versions is more than the default paginiated per page' do
+ before do
+ allow(Kaminari.config).to receive(:default_per_page).and_return(3)
+ end
+
+ it "returns an arrary containing the first page of commits for the page" do
+ expect(subject.versions).to be_a(::CommitCollection)
+ expect(subject.versions.length).to eq(3)
+ expect(subject.versions.first.id).to eql(subject.last_version.id)
+ end
+
+ it "returns an arrary containing the second page of commits for the page with options[:page] = 2" do
+ versions = subject.versions(page: 2)
+ expect(versions).to be_a(::CommitCollection)
+ expect(versions.length).to eq(1)
+ end
+ end
+
+ context "wiki repository's default is updated" do
+ before do
+ force_wiki_change_branch
+ end
+
+ it "returns the correct versions in the default branch" do
+ page = container.wiki.find_page(subject.title)
- it "returns an array of all commits for the page" do
+ expect(page.versions).to be_a(::CommitCollection)
+ expect(page.versions.length).to eq(4)
+ expect(page.versions.first.id).to eql(page.last_version.id)
+
+ page.update(content: "final content")
+ expect(page.versions.length).to eq(5)
+ end
+ end
+ end
+
+ describe "#count_versions" do
+ let(:subject) { create_wiki_page(container) }
+
+ it "returns the total numbers of commits" do
expect do
3.times { |i| subject.update(content: "content #{i}") }
- end.to change { subject.versions.count }.by(3)
+ end.to change(subject, :count_versions).from(1).to(4)
+ end
+
+ context "wiki repository's default is updated" do
+ before do
+ subject
+ force_wiki_change_branch
+ end
+
+ it "returns the correct number of versions in the default branch" do
+ page = container.wiki.find_page(subject.title)
+ expect(page.count_versions).to eq(1)
+
+ page.update(content: "final content")
+ expect(page.count_versions).to eq(2)
+ end
end
end
describe '#title_changed?' do
using RSpec::Parameterized::TableSyntax
- let_it_be(:unsaved_page) { build_wiki_page(title: 'test page') }
- let_it_be(:existing_page) { create_wiki_page(title: 'test page') }
- let_it_be(:directory_page) { create_wiki_page(title: 'parent directory/child page') }
- let_it_be(:page_with_special_characters) { create_wiki_page(title: 'test+page') }
+ let(:unsaved_page) { build_wiki_page(container, title: 'test page') }
+ let(:existing_page) { create_wiki_page(container, title: 'test page') }
+ let(:directory_page) { create_wiki_page(container, title: 'parent directory/child page') }
+ let(:page_with_special_characters) { create_wiki_page(container, title: 'test+page') }
let(:untitled_page) { described_class.new(wiki) }
@@ -704,7 +776,7 @@ RSpec.describe WikiPage do
describe '#content_changed?' do
context 'with a new page' do
- subject { build_wiki_page }
+ subject { build_wiki_page(container) }
it 'returns true if content is set' do
subject.attributes[:content] = 'new'
@@ -756,13 +828,13 @@ RSpec.describe WikiPage do
describe '#path' do
it 'returns the path when persisted' do
- existing_page = create_wiki_page(title: 'path test')
+ existing_page = create_wiki_page(container, title: 'path test')
expect(existing_page.path).to eq('path-test.md')
end
it 'returns nil when not persisted' do
- unsaved_page = build_wiki_page(title: 'path test')
+ unsaved_page = build_wiki_page(container, title: 'path test')
expect(unsaved_page.path).to be_nil
end
@@ -789,7 +861,7 @@ RSpec.describe WikiPage do
describe '#historical?' do
let!(:container) { create(:project) }
- subject { create_wiki_page }
+ subject { create_wiki_page(container) }
let(:wiki) { subject.wiki }
let(:old_version) { subject.versions.last.id }
@@ -830,17 +902,17 @@ RSpec.describe WikiPage do
describe '#persisted?' do
it 'returns true for a persisted page' do
- expect(create_wiki_page).to be_persisted
+ expect(create_wiki_page(container)).to be_persisted
end
it 'returns false for an unpersisted page' do
- expect(build_wiki_page).not_to be_persisted
+ expect(build_wiki_page(container)).not_to be_persisted
end
end
describe '#to_partial_path' do
it 'returns the relative path to the partial to be used' do
- expect(build_wiki_page.to_partial_path).to eq('../shared/wikis/wiki_page')
+ expect(build_wiki_page(container).to_partial_path).to eq('../shared/wikis/wiki_page')
end
end
@@ -868,7 +940,7 @@ RSpec.describe WikiPage do
end
it 'returns false for page with different slug on same container' do
- other_page = create_wiki_page
+ other_page = create_wiki_page(container)
expect(subject.slug).not_to eq(other_page.slug)
expect(subject.container).to eq(other_page.container)
@@ -902,7 +974,7 @@ RSpec.describe WikiPage do
end
describe '#hook_attrs' do
- subject { build_wiki_page }
+ subject { build_wiki_page(container) }
it 'adds absolute urls for images in the content' do
subject.attributes[:content] = 'test![WikiPage_Image](/uploads/abc/WikiPage_Image.png)'
@@ -914,13 +986,13 @@ RSpec.describe WikiPage do
describe '#version_commit_timestamp' do
context 'for a new page' do
it 'returns nil' do
- expect(build_wiki_page.version_commit_timestamp).to be_nil
+ expect(build_wiki_page(container).version_commit_timestamp).to be_nil
end
end
context 'for page that exists' do
it 'returns the timestamp of the commit' do
- existing_page = create_wiki_page
+ existing_page = create_wiki_page(container)
expect(existing_page.version_commit_timestamp).to eq(existing_page.version.commit.committed_date)
end
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index 73b23e8b701..eef6dfe9422 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -90,11 +90,10 @@ ci_cd_settings:
- id
- project_id
- group_runners_enabled
- - merge_pipelines_enabled
- merge_trains_enabled
- merge_pipelines_enabled
- - merge_trains_enabled
- auto_rollback_enabled
+ - inbound_job_token_scope_enabled
remapped_attributes:
default_git_depth: ci_default_git_depth
forward_deployment_enabled: ci_forward_deployment_enabled
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index 009cf102c18..b9c273b1e1a 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -338,6 +338,41 @@ RSpec.shared_examples 'wiki model' do
end
end
+ context "wiki repository's default branch is updated" do
+ before do
+ old_default_branch = wiki.default_branch
+ subject.create_page('page in updated default branch', 'content')
+ subject.repository.add_branch(user, 'another_branch', old_default_branch)
+ subject.repository.rm_branch(user, old_default_branch)
+ subject.repository.expire_status_cache
+ end
+
+ it 'returns the page in the updated default branch' do
+ wiki = described_class.new(wiki_container, user)
+ page = wiki.find_page('page in updated default branch')
+
+ expect(wiki.default_branch).to eql('another_branch')
+ expect(page.title).to eq('page in updated default branch')
+ end
+ end
+
+ context "wiki repository's HEAD is updated" do
+ before do
+ subject.create_page('page in updated HEAD', 'content')
+ subject.repository.add_branch(user, 'another_branch', subject.default_branch)
+ subject.repository.change_head('another_branch')
+ subject.repository.expire_status_cache
+ end
+
+ it 'returns the page in the new HEAD' do
+ wiki = described_class.new(wiki_container, user)
+ page = subject.find_page('page in updated HEAD')
+
+ expect(wiki.default_branch).to eql('another_branch')
+ expect(page.title).to eq('page in updated HEAD')
+ end
+ end
+
context 'pages with different file extensions' do
where(:extension, :path, :title) do
[
@@ -456,6 +491,22 @@ RSpec.shared_examples 'wiki model' do
expect(file.raw_data).to be_empty
end
end
+
+ context "wiki repository's default branch is updated" do
+ before do
+ old_default_branch = wiki.default_branch
+ subject.repository.add_branch(user, 'another_branch', old_default_branch)
+ subject.repository.rm_branch(user, old_default_branch)
+ subject.repository.expire_status_cache
+ end
+
+ it 'returns the page in the updated default branch' do
+ wiki = described_class.new(wiki_container, user)
+ file = wiki.find_file('image.png')
+
+ expect(file.mime_type).to eq('image/png')
+ end
+ end
end
describe '#create_page' do
diff --git a/spec/uploaders/object_storage/cdn/google_cdn_spec.rb b/spec/uploaders/object_storage/cdn/google_cdn_spec.rb
index b72f6d66d69..1403bbaa060 100644
--- a/spec/uploaders/object_storage/cdn/google_cdn_spec.rb
+++ b/spec/uploaders/object_storage/cdn/google_cdn_spec.rb
@@ -68,6 +68,26 @@ RSpec.describe ObjectStorage::CDN::GoogleCDN,
expect(subject.use_cdn?(public_ip)).to be false
end
end
+
+ context 'when URL is a domain' do
+ before do
+ options[:url] = 'cdn.gitlab.example.com'
+ end
+
+ it 'returns false' do
+ expect(subject.use_cdn?(public_ip)).to be false
+ end
+ end
+
+ context 'when URL uses HTTP' do
+ before do
+ options[:url] = 'http://cdn.gitlab.example.com'
+ end
+
+ it 'returns false' do
+ expect(subject.use_cdn?(public_ip)).to be false
+ end
+ end
end
describe '#signed_url' do