Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-04 03:19:33 +00:00
parent 3e1ab18831
commit b6927b3dd6
41 changed files with 3313 additions and 684 deletions

View File

@ -136,6 +136,7 @@ export default {
v-if="!isCurrentBranchTarget"
v-model="openMergeRequest"
data-testid="new-mr-checkbox"
data-qa-selector="new_mr_checkbox"
class="gl-mt-3"
>
<gl-sprintf :message="$options.i18n.startMergeRequest">

View File

@ -41,7 +41,12 @@ export default {
</template>
</gl-sprintf>
</p>
<gl-button variant="confirm" class="gl-mt-3" @click="createEmptyConfigFile">
<gl-button
variant="confirm"
class="gl-mt-3"
data-qa-selector="create_new_ci_button"
@click="createEmptyConfigFile"
>
{{ $options.i18n.btnText }}
</gl-button>
</div>

View File

@ -1,3 +1,5 @@
import { s__ } from '~/locale';
// Values for CI_CONFIG_STATUS_* comes from lint graphQL
export const CI_CONFIG_STATUS_INVALID = 'INVALID';
export const CI_CONFIG_STATUS_VALID = 'VALID';
@ -62,3 +64,45 @@ export const TEMPLATE_REPOSITORY_URL =
'https://gitlab.com/gitlab-org/gitlab-foss/tree/master/lib/gitlab/ci/templates';
export const COMMIT_SHA_POLL_INTERVAL = 1000;
export const RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME = 'runners_availability_section';
export const RUNNERS_SETTINGS_LINK_CLICKED_EVENT = 'runners_settings_link_clicked';
export const RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT = 'runners_documentation_link_clicked';
export const RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT = 'runners_settings_button_clicked';
export const I18N = {
title: s__('Pipelines|Get started with GitLab CI/CD'),
runners: {
title: s__('Pipelines|Runners are available to run your jobs now'),
subtitle: s__(
'Pipelines|GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. There are active runners available to run your jobs right now. If you prefer, you can %{settingsLinkStart}configure your runners%{settingsLinkEnd} or %{docsLinkStart}learn more%{docsLinkEnd} about runners.',
),
},
noRunners: {
title: s__('Pipelines|No runners detected'),
subtitle: s__(
'Pipelines|A GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. Install GitLab Runner and register your own runners to get started with CI/CD.',
),
cta: s__('Pipelines|Install GitLab Runner'),
},
learnBasics: {
title: s__('Pipelines|Learn the basics of pipelines and .yml files'),
subtitle: s__(
'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.',
),
gettingStarted: {
title: s__('Pipelines|"Hello world" with GitLab CI'),
description: s__(
'Pipelines|Get familiar with GitLab CI syntax by setting up a simple pipeline running a "Hello world" script to see how it runs, explore how CI/CD works.',
),
cta: s__('Pipelines|Try test template'),
},
},
templates: {
title: s__('Pipelines|Ready to set up CI/CD for your project?'),
subtitle: s__(
"Pipelines|Use a template based on your project's language or framework to get started with GitLab CI/CD.",
),
description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
cta: s__('Pipelines|Use template'),
},
};

View File

@ -49,6 +49,11 @@ export default {
required: false,
default: null,
},
anyRunnersAvailable: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
ciHelpPagePath() {
@ -120,7 +125,11 @@ export default {
</gl-empty-state>
</template>
</gitlab-experiment>
<pipelines-ci-templates v-else-if="canSetCi" />
<pipelines-ci-templates
v-else-if="canSetCi"
:ci-runner-settings-path="ciRunnerSettingsPath"
:any-runners-available="anyRunnersAvailable"
/>
<gl-empty-state
v-else
title=""

View File

@ -112,6 +112,11 @@ export default {
required: false,
default: null,
},
anyRunnersAvailable: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
@ -382,6 +387,7 @@ export default {
:can-set-ci="canCreatePipeline"
:code-quality-page-path="codeQualityPagePath"
:ci-runner-settings-path="ciRunnerSettingsPath"
:any-runners-available="anyRunnersAvailable"
/>
<gl-empty-state

View File

@ -1,8 +1,19 @@
<script>
import { GlAvatar, GlButton, GlCard, GlSprintf } from '@gitlab/ui';
import { GlAvatar, GlButton, GlCard, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale';
import { STARTER_TEMPLATE_NAME } from '~/pipeline_editor/constants';
import { sprintf } from '~/locale';
import {
STARTER_TEMPLATE_NAME,
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
} from '~/pipeline_editor/constants';
import { helpPagePath } from '~/helpers/help_page_helper';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { getExperimentData } from '~/experimentation/utils';
import Tracking from '~/tracking';
export default {
@ -11,39 +22,37 @@ export default {
GlButton,
GlCard,
GlSprintf,
GlIcon,
GlLink,
GitlabExperiment,
},
mixins: [Tracking.mixin()],
STARTER_TEMPLATE_NAME,
i18n: {
cta: s__('Pipelines|Use template'),
testTemplates: {
title: s__('Pipelines|Use a sample CI/CD template'),
subtitle: s__(
'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.',
),
gettingStarted: {
title: s__('Pipelines|Get started with GitLab CI/CD'),
description: s__(
'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline.',
),
},
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
props: {
ciRunnerSettingsPath: {
type: String,
required: false,
default: null,
},
templates: {
title: s__('Pipelines|Use a CI/CD template'),
subtitle: s__(
"Pipelines|Use a template based on your project's language or framework to get started with GitLab CI/CD.",
),
description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
anyRunnersAvailable: {
type: Boolean,
required: false,
default: true,
},
},
inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
data() {
const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
return {
name,
logo,
link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
description: sprintf(this.$options.i18n.templates.description, { name }),
description: sprintf(this.$options.I18N.templates.description, { name }),
};
});
@ -53,39 +62,104 @@ export default {
{ template: STARTER_TEMPLATE_NAME },
this.pipelineEditorPath,
),
tracker: null,
};
},
computed: {
sharedRunnersHelpPagePath() {
return helpPagePath('ci/runners/runners_scope', { anchor: 'shared-runners' });
},
runnersAvailabilitySectionExperimentEnabled() {
return Boolean(getExperimentData(RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME));
},
},
created() {
this.tracker = new ExperimentTracking(RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME);
},
methods: {
trackEvent(template) {
this.track('template_clicked', {
label: template,
});
},
trackExperimentEvent(action) {
this.tracker.event(action);
},
},
};
</script>
<template>
<div>
<h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.testTemplates.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">
<gl-sprintf :message="$options.i18n.testTemplates.subtitle">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.I18N.title }}</h2>
<div class="row gl-mb-8">
<div class="col-12">
<gitlab-experiment :name="$options.RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME">
<template #candidate>
<div v-if="anyRunnersAvailable">
<h2 class="gl-font-base gl-text-gray-900">
<gl-icon name="check-circle-filled" class="gl-text-green-500 gl-mr-2" :size="12" />
{{ $options.I18N.runners.title }}
</h2>
<p class="gl-text-gray-800 gl-mb-6">
<gl-sprintf :message="$options.I18N.runners.subtitle">
<template #settingsLink="{ content }">
<gl-link
data-testid="settings-link"
:href="ciRunnerSettingsPath"
@click="trackExperimentEvent($options.RUNNERS_SETTINGS_LINK_CLICKED_EVENT)"
>{{ content }}</gl-link
>
</template>
<template #docsLink="{ content }">
<gl-link
data-testid="documentation-link"
:href="sharedRunnersHelpPagePath"
@click="trackExperimentEvent($options.RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT)"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</p>
</div>
<div v-else>
<h2 class="gl-font-base gl-text-gray-900">
<gl-icon name="warning-solid" class="gl-text-red-600 gl-mr-2" :size="14" />
{{ $options.I18N.noRunners.title }}
</h2>
<p class="gl-text-gray-800 gl-mb-6">{{ $options.I18N.noRunners.subtitle }}</p>
<gl-button
data-testid="settings-button"
category="primary"
variant="confirm"
:href="ciRunnerSettingsPath"
@click="trackExperimentEvent($options.RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT)"
>
{{ $options.I18N.noRunners.cta }}
</gl-button>
</div>
</template>
</gitlab-experiment>
<template v-if="!runnersAvailabilitySectionExperimentEnabled || anyRunnersAvailable">
<h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.learnBasics.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">
<gl-sprintf :message="$options.I18N.learnBasics.subtitle">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<div class="gl-lg-w-25p gl-lg-pr-5 gl-mb-8">
<gl-card>
<div class="gl-flex-direction-row">
<div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>
<div class="gl-mb-3">
<strong class="gl-text-gray-800 gl-mb-2">{{
$options.i18n.testTemplates.gettingStarted.title
}}</strong>
<strong class="gl-text-gray-800 gl-mb-2">
{{ $options.I18N.learnBasics.gettingStarted.title }}
</strong>
</div>
<p class="gl-font-sm">{{ $options.i18n.testTemplates.gettingStarted.description }}</p>
<p class="gl-font-sm">{{ $options.I18N.learnBasics.gettingStarted.description }}</p>
</div>
<gl-button
@ -95,51 +169,51 @@ export default {
data-testid="test-template-link"
@click="trackEvent($options.STARTER_TEMPLATE_NAME)"
>
{{ $options.i18n.cta }}
{{ $options.I18N.learnBasics.gettingStarted.cta }}
</gl-button>
</gl-card>
</div>
</div>
<h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.templates.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">{{ $options.i18n.templates.subtitle }}</p>
<h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.templates.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">{{ $options.I18N.templates.subtitle }}</p>
<ul class="gl-list-style-none gl-pl-0">
<li v-for="template in templates" :key="template.name">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3"
>
<div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
<gl-avatar
:src="template.logo"
:size="64"
class="gl-mr-6 gl-bg-white dark-mode-override"
shape="rect"
:alt="template.name"
data-testid="template-logo"
/>
<div class="gl-flex-direction-row">
<div class="gl-mb-3">
<strong class="gl-text-gray-800" data-testid="template-name">{{
template.name
}}</strong>
</div>
<p class="gl-mb-0 gl-font-sm" data-testid="template-description">
{{ template.description }}
</p>
</div>
</div>
<gl-button
category="primary"
variant="confirm"
:href="template.link"
data-testid="template-link"
@click="trackEvent(template.name)"
<ul class="gl-list-style-none gl-pl-0">
<li v-for="template in templates" :key="template.name">
<div
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3"
>
{{ $options.i18n.cta }}
</gl-button>
</div>
</li>
</ul>
<div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
<gl-avatar
:src="template.logo"
:size="48"
class="gl-mr-5 gl-bg-white dark-mode-override"
shape="rect"
:alt="template.name"
data-testid="template-logo"
/>
<div class="gl-flex-direction-row">
<div class="gl-mb-3">
<strong class="gl-text-gray-800" data-testid="template-name">
{{ template.name }}
</strong>
</div>
<p class="gl-mb-0 gl-font-sm" data-testid="template-description">
{{ template.description }}
</p>
</div>
</div>
<gl-button
category="primary"
variant="confirm"
:href="template.link"
data-testid="template-link"
@click="trackEvent(template.name)"
>
{{ $options.I18N.templates.cta }}
</gl-button>
</div>
</li>
</ul>
</template>
</div>
</template>

View File

@ -39,6 +39,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
params,
codeQualityPagePath,
ciRunnerSettingsPath,
anyRunnersAvailable,
} = el.dataset;
return new Vue({
@ -78,6 +79,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
params: JSON.parse(params),
codeQualityPagePath,
ciRunnerSettingsPath,
anyRunnersAvailable: parseBoolean(anyRunnersAvailable),
},
});
},

View File

@ -56,6 +56,7 @@ class Projects::PipelinesController < Projects::ApplicationController
format.html do
enable_code_quality_walkthrough_experiment
enable_ci_runner_templates_experiment
enable_runners_availability_section_experiment
end
format.json do
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
@ -335,6 +336,18 @@ class Projects::PipelinesController < Projects::ApplicationController
end
end
def enable_runners_availability_section_experiment
return unless current_user
return unless can?(current_user, :create_pipeline, project)
return if @pipelines_count.to_i > 0
return if helpers.has_gitlab_ci?(project)
experiment(:runners_availability_section, namespace: project.root_ancestor) do |e|
e.candidate {}
e.publish_to_database
end
end
def should_track_ci_cd_pipelines?
params[:chart].blank? || params[:chart] == 'pipelines'
end

View File

@ -78,6 +78,37 @@ module Ci
pipeline.stuck?
end
def pipelines_list_data(project, list_url)
artifacts_endpoint_placeholder = ':pipeline_artifacts_id'
data = {
endpoint: list_url,
project_id: project.id,
params: params.to_json,
artifacts_endpoint: downloadable_artifacts_project_pipeline_path(project, artifacts_endpoint_placeholder, format: :json),
artifacts_endpoint_placeholder: artifacts_endpoint_placeholder,
pipeline_schedule_url: pipeline_schedules_path(project),
empty_state_svg_path: image_path('illustrations/pipelines_empty.svg'),
error_state_svg_path: image_path('illustrations/pipelines_failed.svg'),
no_pipelines_svg_path: image_path('illustrations/pipelines_pending.svg'),
can_create_pipeline: can?(current_user, :create_pipeline, project).to_s,
new_pipeline_path: can?(current_user, :create_pipeline, project) && new_project_pipeline_path(project),
ci_lint_path: can?(current_user, :create_pipeline, project) && project_ci_lint_path(project),
reset_cache_path: can?(current_user, :admin_pipeline, project) && reset_cache_project_settings_ci_cd_path(project),
has_gitlab_ci: has_gitlab_ci?(project).to_s,
pipeline_editor_path: can?(current_user, :create_pipeline, project) && project_ci_pipeline_editor_path(project),
suggested_ci_templates: suggested_ci_templates.to_json,
code_quality_page_path: project.present(current_user: current_user).add_code_quality_ci_yml_path,
ci_runner_settings_path: project_settings_ci_cd_path(project, ci_runner_templates: true, anchor: 'js-runners-settings')
}
experiment(:runners_availability_section, namespace: project.root_ancestor) do |e|
e.candidate { data[:any_runners_available] = project.active_runners.exists?.to_s }
end
data
end
private
def warning_markdown(pipeline)

View File

@ -1,28 +1,10 @@
- page_title _('Pipelines')
- add_page_specific_style 'page_bundles/pipelines'
- add_page_specific_style 'page_bundles/ci_status'
- artifacts_endpoint_placeholder = ':pipeline_artifacts_id'
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
- list_url = project_pipelines_path(@project, format: :json, code_quality_walkthrough: params[:code_quality_walkthrough])
- add_page_startup_api_call list_url
#pipelines-list-vue{ data: { endpoint: list_url,
project_id: @project.id,
params: params.to_json,
"artifacts-endpoint" => downloadable_artifacts_project_pipeline_path(@project, artifacts_endpoint_placeholder, format: :json),
"artifacts-endpoint-placeholder" => artifacts_endpoint_placeholder,
"pipeline-schedule-url" => pipeline_schedules_path(@project),
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
"error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
"no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project),
"ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project),
"reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project),
"has-gitlab-ci" => has_gitlab_ci?(@project).to_s,
"pipeline-editor-path" => can?(current_user, :create_pipeline, @project) && project_ci_pipeline_editor_path(@project),
"suggested-ci-templates" => suggested_ci_templates.to_json,
"code-quality-page-path" => @project.present(current_user: current_user).add_code_quality_ci_yml_path,
"ci-runner-settings-path" => project_settings_ci_cd_path(@project, ci_runner_templates: true, anchor: 'js-runners-settings') } }
#pipelines-list-vue{ data: pipelines_list_data(@project, list_url) }

View File

@ -0,0 +1,8 @@
---
name: runners_availability_section
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80717
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352850
milestone: '14.9'
type: experiment
group: group::activation
default_enabled: false

View File

@ -68,3 +68,6 @@ You can configure the following security controls:
- [License Compliance](../../../user/compliance/license_compliance/index.md)
- Can be configured with `.gitlab-ci.yml`. For more details, read [License Compliance](../../../user/compliance/license_compliance/index.md#enable-license-compliance).
- [Security Training](../../../user/application_security/vulnerabilities/index.md#enable-security-training-for-vulnerabilities)
- Enable **Security training** for the current project. For more details, read [security training](../../../user/application_security/vulnerabilities/index.md#enable-security-training-for-vulnerabilities).

View File

@ -30,6 +30,8 @@ On the vulnerability's page, you can:
- [Resolve a vulnerability](#resolve-a-vulnerability), if a solution is
available.
In GitLab 14.9 and later, if security training is enabled, the vulnerability page includes a training link relevant to the detected vulnerability.
## Vulnerability status values
A vulnerability's status can be one of the following:
@ -159,3 +161,25 @@ To manually apply the patch that GitLab generated for a vulnerability:
1. Ensure your local project has the same commit checked out that was used to generate the patch.
1. Run `git apply remediation.patch`.
1. Verify and commit the changes to your branch.
## Enable security training for vulnerabilities
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6176) in GitLab 14.9.
Security training helps your developers learn how to fix vulnerabilities. Developers can view security training from selected educational providers, relevant to the detected vulnerability.
To enable security training for vulnerabilities in your project:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Security & Compliance > Configuration**.
1. On the tab bar, select **Vulnerability Management**.
1. To enable a security training provider, turn on the toggle.
## View security training for a vulnerability
To view the security training for a vulnerability:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Security & Compliance > Vulnerability report**.
1. Select the vulnerability for which you want to view security training.
1. If the security training provider supports training for the vulnerability, select **View training**.

View File

@ -50,7 +50,7 @@ module Gitlab
HTTP_PROXY_ENV_VARS = %w(http_proxy https_proxy HTTP_PROXY HTTPS_PROXY).freeze
def self.simulate_com?
return false unless Rails.env.test? || Rails.env.development?
return false unless Rails.env.development?
Gitlab::Utils.to_boolean(ENV['GITLAB_SIMULATE_SAAS'])
end

View File

@ -26912,6 +26912,12 @@ msgstr ""
msgid "Pipelines settings for '%{project_name}' were successfully updated."
msgstr ""
msgid "Pipelines|\"Hello world\" with GitLab CI"
msgstr ""
msgid "Pipelines|A GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. Install GitLab Runner and register your own runners to get started with CI/CD."
msgstr ""
msgid "Pipelines|API"
msgstr ""
@ -26963,7 +26969,7 @@ msgstr ""
msgid "Pipelines|Editor"
msgstr ""
msgid "Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline."
msgid "Pipelines|Get familiar with GitLab CI syntax by setting up a simple pipeline running a \"Hello world\" script to see how it runs, explore how CI/CD works."
msgstr ""
msgid "Pipelines|Get started with GitLab CI/CD"
@ -26972,12 +26978,18 @@ msgstr ""
msgid "Pipelines|GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time consuming tasks, so you can spend more time creating."
msgstr ""
msgid "Pipelines|GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. There are active runners available to run your jobs right now. If you prefer, you can %{settingsLinkStart}configure your runners%{settingsLinkEnd} or %{docsLinkStart}learn more%{docsLinkEnd} about runners."
msgstr ""
msgid "Pipelines|If you are unsure, please ask a project maintainer to review it for you."
msgstr ""
msgid "Pipelines|Improve code quality with GitLab CI/CD"
msgstr ""
msgid "Pipelines|Install GitLab Runner"
msgstr ""
msgid "Pipelines|Install GitLab Runners"
msgstr ""
@ -26990,6 +27002,9 @@ msgstr ""
msgid "Pipelines|Learn about Runners"
msgstr ""
msgid "Pipelines|Learn the basics of pipelines and .yml files"
msgstr ""
msgid "Pipelines|Lint"
msgstr ""
@ -27005,6 +27020,9 @@ msgstr ""
msgid "Pipelines|More Information"
msgstr ""
msgid "Pipelines|No runners detected"
msgstr ""
msgid "Pipelines|No triggers have been created yet. Add one using the form above."
msgstr ""
@ -27017,9 +27035,15 @@ msgstr ""
msgid "Pipelines|Project cache successfully reset."
msgstr ""
msgid "Pipelines|Ready to set up CI/CD for your project?"
msgstr ""
msgid "Pipelines|Revoke trigger"
msgstr ""
msgid "Pipelines|Runners are available to run your jobs now"
msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
@ -27083,15 +27107,12 @@ msgstr ""
msgid "Pipelines|Trigger user has insufficient permissions to project"
msgstr ""
msgid "Pipelines|Use a CI/CD template"
msgid "Pipelines|Try test template"
msgstr ""
msgid "Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works."
msgstr ""
msgid "Pipelines|Use a sample CI/CD template"
msgstr ""
msgid "Pipelines|Use a template based on your project's language or framework to get started with GitLab CI/CD."
msgstr ""

View File

@ -116,7 +116,7 @@ module QA
end
view 'app/views/projects/merge_requests/_mr_box.html.haml' do
element :title_content
element :title_content, required: true
end
view 'app/views/projects/merge_requests/_mr_title.html.haml' do
@ -124,9 +124,9 @@ module QA
end
view 'app/views/projects/merge_requests/show.html.haml' do
element :notes_tab
element :commits_tab
element :diffs_tab
element :notes_tab, required: true
element :commits_tab, required: true
element :diffs_tab, required: true
end
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue' do

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module QA
module Page
module Project
module PipelineEditor
class New < QA::Page::Base
view 'app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue' do
element :create_new_ci_button, required: true
end
def create_new_ci
click_element(:create_new_ci_button, Page::Project::PipelineEditor::Show)
end
end
end
end
end
end

View File

@ -6,13 +6,13 @@ module QA
module PipelineEditor
class Show < QA::Page::Base
view 'app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue' do
element :branch_selector_button, require: true
element :branch_selector_button, required: true
element :branch_menu_item_button
element :branch_menu_container
end
view 'app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue' do
element :target_branch_field, require: true
element :target_branch_field, required: true
end
view 'app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue' do
@ -21,7 +21,7 @@ module QA
end
view 'app/assets/javascripts/vue_shared/components/source_editor.vue' do
element :source_editor_container, require: true
element :source_editor_container, required: true
end
view 'app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue' do
@ -30,6 +30,7 @@ module QA
view 'app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue' do
element :commit_changes_button
element :new_mr_checkbox
end
view 'app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue' do
@ -127,6 +128,18 @@ module QA
end
end
def has_new_mr_checkbox?
has_element?(:new_mr_checkbox, visible: true)
end
def has_no_new_mr_checkbox?
has_no_element?(:new_mr_checkbox, visible: true)
end
def select_new_mr_checkbox
check_element(:new_mr_checkbox, true)
end
private
def go_to_tab(name)

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Verify' do
describe 'Pipeline editor' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'pipeline-editor-project'
project.initialize_with_readme = true
end
end
before do
Flow::Login.sign_in
project.visit!
Page::Project::Menu.perform(&:go_to_pipeline_editor)
end
after do
project&.remove_via_api!
end
it(
'can create merge request',
test_case: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349130'
) do
Page::Project::PipelineEditor::New.perform(&:create_new_ci)
Page::Project::PipelineEditor::Show.perform do |show|
# Editor should display default content when project does not have CI file yet
# New MR checkbox should not be rendered when a new target branch is yet to be provided
aggregate_failures 'check editor default conditions' do
expect(show.editing_content).not_to be_empty
expect(show).to have_no_new_mr_checkbox
end
# The new MR checkbox is visible after a new target branch name is set
show.set_target_branch(SecureRandom.hex(10))
expect(show).to have_new_mr_checkbox
show.select_new_mr_checkbox
show.submit_changes
end
Page::MergeRequest::New.perform(&:create_merge_request)
Page::MergeRequest::Show.perform do |show|
expect(show).to have_title('Update .gitlab-ci.yml file')
end
end
end
end
end

View File

@ -299,6 +299,10 @@ RSpec.describe Projects::PipelinesController do
context 'ci_runner_templates experiment' do
it_behaves_like 'tracks assignment and records the subject', :ci_runner_templates, :namespace
end
context 'runners_availability_section experiment' do
it_behaves_like 'tracks assignment and records the subject', :runners_availability_section, :namespace
end
end
describe 'GET #show' do

View File

@ -1033,71 +1033,6 @@ RSpec.describe 'File blob', :js do
stub_feature_flags(refactor_blob_viewer: false)
end
context 'when ref switch' do
# We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled
# This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351558
def switch_ref_to(ref_name)
first('.qa-branches-select').click # rubocop:disable QA/SelectorUsage
page.within '.project-refs-form' do
click_link ref_name
wait_for_requests
end
end
context 'when highlighting lines' do
it 'displays single highlighted line number of different ref' do
visit_blob('files/js/application.js', anchor: 'L1')
switch_ref_to('feature')
page.within '.blob-content' do
expect(find_by_id('LC1')[:class]).to include("hll")
end
end
it 'displays multiple highlighted line numbers of different ref' do
visit_blob('files/js/application.js', anchor: 'L1-3')
switch_ref_to('feature')
page.within '.blob-content' do
expect(find_by_id('LC1')[:class]).to include("hll")
expect(find_by_id('LC2')[:class]).to include("hll")
expect(find_by_id('LC3')[:class]).to include("hll")
end
end
end
end
context 'visiting with a line number anchor' do
# We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled
# This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351558
before do
visit_blob('files/markdown/ruby-style-guide.md', anchor: 'L1')
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).not_to have_selector('.blob-viewer[data-type="rich"]')
# highlights the line in question
expect(page).to have_selector('#LC1.hll')
# shows highlighted Markdown code
expect(page).to have_css(".js-syntax-highlight")
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
context 'binary file that appears to be text in the first 1024 bytes' do
# We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled
# This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351559
@ -1158,503 +1093,5 @@ RSpec.describe 'File blob', :js do
end
end
end
context 'files with auxiliary viewers' do
# This context is the same as the other 'files with auxiliary viewers' in this file, we just ensure that the auxiliary viewers still work this the refactor_blob_viewer disabled
# It should be safe to remove once we rollout the refactored blob viewer
describe '.gitlab-ci.yml' do
before do
project.add_maintainer(project.creator)
Files::CreateService.new(
project,
project.creator,
start_branch: 'master',
branch_name: 'master',
commit_message: "Add .gitlab-ci.yml",
file_path: '.gitlab-ci.yml',
file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
).execute
visit_blob('.gitlab-ci.yml')
end
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that configuration is valid
expect(page).to have_content('This GitLab CI configuration is valid.')
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
describe '.gitlab/route-map.yml' do
before do
project.add_maintainer(project.creator)
Files::CreateService.new(
project,
project.creator,
start_branch: 'master',
branch_name: 'master',
commit_message: "Add .gitlab/route-map.yml",
file_path: '.gitlab/route-map.yml',
file_content: <<-MAP.strip_heredoc
# Team data
- source: 'data/team.yml'
public: 'team/'
MAP
).execute
visit_blob('.gitlab/route-map.yml')
end
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that map is valid
expect(page).to have_content('This Route Map is valid.')
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
describe '.gitlab/dashboards/custom-dashboard.yml' do
before do
project.add_maintainer(project.creator)
Files::CreateService.new(
project,
project.creator,
start_branch: 'master',
branch_name: 'master',
commit_message: "Add .gitlab/dashboards/custom-dashboard.yml",
file_path: '.gitlab/dashboards/custom-dashboard.yml',
file_content: file_content
).execute
end
context 'with metrics_dashboard_exhaustive_validations feature flag off' do
before do
stub_feature_flags(metrics_dashboard_exhaustive_validations: false)
visit_blob('.gitlab/dashboards/custom-dashboard.yml')
end
context 'valid dashboard file' do
let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) }
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that dashboard yaml is valid
expect(page).to have_content('Metrics Dashboard YAML definition is valid.')
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
context 'invalid dashboard file' do
let(:file_content) { "dashboard: 'invalid'" }
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that dashboard yaml is invalid
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
expect(page).to have_content("panel_groups: should be an array of panel_groups objects")
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
end
context 'with metrics_dashboard_exhaustive_validations feature flag on' do
before do
stub_feature_flags(metrics_dashboard_exhaustive_validations: true)
visit_blob('.gitlab/dashboards/custom-dashboard.yml')
end
context 'valid dashboard file' do
let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) }
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that dashboard yaml is valid
expect(page).to have_content('Metrics Dashboard YAML definition is valid.')
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
context 'invalid dashboard file' do
let(:file_content) { "dashboard: 'invalid'" }
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows that dashboard yaml is invalid
expect(page).to have_content('Metrics Dashboard YAML definition is invalid:')
expect(page).to have_content("root is missing required keys: panel_groups")
# shows a learn more link
expect(page).to have_link('Learn more')
end
end
end
end
end
context 'LICENSE' do
before do
visit_blob('LICENSE')
end
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows license
expect(page).to have_content('This project is licensed under the MIT License.')
# shows a learn more link
expect(page).to have_link('Learn more', href: 'http://choosealicense.com/licenses/mit/')
end
end
end
context '*.gemspec' do
before do
project.add_maintainer(project.creator)
Files::CreateService.new(
project,
project.creator,
start_branch: 'master',
branch_name: 'master',
commit_message: "Add activerecord.gemspec",
file_path: 'activerecord.gemspec',
file_content: <<-SPEC.strip_heredoc
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = "activerecord"
end
SPEC
).execute
visit_blob('activerecord.gemspec')
end
it 'displays an auxiliary viewer' do
aggregate_failures do
# shows names of dependency manager and package
expect(page).to have_content('This project manages its dependencies using RubyGems.')
# shows a learn more link
expect(page).to have_link('Learn more', href: 'https://rubygems.org/')
end
end
end
context 'CONTRIBUTING.md' do
before do
file_name = 'CONTRIBUTING.md'
create_file(file_name, '## Contribution guidelines')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("After you've reviewed these contribution guidelines, you'll be all set to contribute to this project.")
end
end
end
context 'CHANGELOG.md' do
before do
file_name = 'CHANGELOG.md'
create_file(file_name, '## Changelog for v1.0.0')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("To find the state of this project's repository at the time of any of these versions, check out the tags.")
end
end
end
context 'Cargo.toml' do
before do
file_name = 'Cargo.toml'
create_file(file_name, '
[package]
name = "hello_world" # the name of the package
version = "0.1.0" # the current version, obeying semver
authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using Cargo.")
end
end
end
context 'Cartfile' do
before do
file_name = 'Cartfile'
create_file(file_name, '
gitlab "Alamofire/Alamofire" == 4.9.0
gitlab "Alamofire/AlamofireImage" ~> 3.4
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using Carthage.")
end
end
end
context 'composer.json' do
before do
file_name = 'composer.json'
create_file(file_name, '
{
"license": "MIT"
}
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using Composer.")
end
end
end
context 'Gemfile' do
before do
file_name = 'Gemfile'
create_file(file_name, '
source "https://rubygems.org"
# Gems here
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using Bundler.")
end
end
end
context 'Godeps.json' do
before do
file_name = 'Godeps.json'
create_file(file_name, '
{
"GoVersion": "go1.6"
}
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using godep.")
end
end
end
context 'go.mod' do
before do
file_name = 'go.mod'
create_file(file_name, '
module example.com/mymodule
go 1.14
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using Go Modules.")
end
end
end
context 'package.json' do
before do
file_name = 'package.json'
create_file(file_name, '
{
"name": "my-awesome-package",
"version": "1.0.0"
}
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using npm.")
end
end
end
context 'podfile' do
before do
file_name = 'podfile'
create_file(file_name, 'platform :ios, "8.0"')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using CocoaPods.")
end
end
end
context 'test.podspec' do
before do
file_name = 'test.podspec'
create_file(file_name, '
Pod::Spec.new do |s|
s.name = "TensorFlowLiteC"
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using CocoaPods.")
end
end
end
context 'JSON.podspec.json' do
before do
file_name = 'JSON.podspec.json'
create_file(file_name, '
{
"name": "JSON"
}
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using CocoaPods.")
end
end
end
context 'requirements.txt' do
before do
file_name = 'requirements.txt'
create_file(file_name, 'Project requirements')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using pip.")
end
end
end
context 'yarn.lock' do
before do
file_name = 'yarn.lock'
create_file(file_name, '
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
')
visit_blob(file_name)
end
it 'displays an auxiliary viewer' do
aggregate_failures do
expect(page).to have_content("This project manages its dependencies using Yarn.")
end
end
end
context 'openapi.yml' do
before do
file_name = 'openapi.yml'
create_file(file_name, '
swagger: \'2.0\'
info:
title: Classic API Resource Documentation
description: |
<div class="foo-bar" style="background-color: red;" data-foo-bar="baz">
<h1>Swagger API documentation</h1>
</div>
version: production
basePath: /JSSResource/
produces:
- application/xml
- application/json
consumes:
- application/xml
- application/json
security:
- basicAuth: []
paths:
/accounts:
get:
responses:
\'200\':
description: No response was specified
tags:
- accounts
operationId: findAccounts
summary: Finds all accounts
')
visit_blob(file_name, useUnsafeMarkdown: '1')
click_button('Display rendered file')
wait_for_requests
end
it 'removes `style`, `class`, and `data-*`` attributes from HTML' do
expect(page).to have_css('h1', text: 'Swagger API documentation')
expect(page).not_to have_css('.foo-bar')
expect(page).not_to have_css('[style="background-color: red;"]')
expect(page).not_to have_css('[data-foo-bar="baz"]')
end
end
end
end
end

View File

@ -913,7 +913,7 @@ RSpec.describe 'Pipelines', :js do
end
it 'renders empty state' do
expect(page).to have_content 'Use a sample CI/CD template'
expect(page).to have_content 'Try test template'
end
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Balsamiq file blob', :js do
let(:project) { create(:project, :public, :repository) }
before do
stub_feature_flags(refactor_blob_viewer: false)
visit project_blob_path(project, 'add-balsamiq-file/files/images/balsamiq.bmpr')
wait_for_requests
end
it 'displays Balsamiq file content' do
expect(page).to have_content("Mobile examples")
end
end

View File

@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do
include TreeHelper
let(:project) { create(:project, :public, :repository) }
let(:path) { 'CHANGELOG' }
let(:sha) { project.repository.commit.sha }
before do
stub_feature_flags(refactor_blob_viewer: false)
end
describe 'On a file(blob)' do
def get_absolute_url(path = "")
"http://#{page.server.host}:#{page.server.port}#{path}"
end
def visit_blob(fragment = nil)
visit project_blob_path(project, tree_join('master', path), anchor: fragment)
end
describe 'Click "Permalink" button' do
it 'works with no initial line number fragment hash' do
visit_blob
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path))))
end
it 'maintains intitial fragment hash' do
fragment = "L3"
visit_blob(fragment)
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
ending_fragment = "L5"
visit_blob
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
fragment = "L1"
ending_fragment = "L5"
visit_blob(fragment)
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
end
end
describe 'Click "Blame" button' do
it 'works with no initial line number fragment hash' do
visit_blob
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path))))
end
it 'maintains intitial fragment hash' do
fragment = "L3"
visit_blob(fragment)
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
ending_fragment = "L5"
visit_blob
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
fragment = "L1"
ending_fragment = "L5"
visit_blob(fragment)
find('#L3').click
find("##{ending_fragment}").click
expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,213 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Editing file blob', :js do
include TreeHelper
include BlobSpecHelpers
let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
let(:branch) { 'master' }
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
let(:readme_file_path) { 'README.md' }
before do
stub_feature_flags(refactor_blob_viewer: false)
end
context 'as a developer' do
let(:user) { create(:user) }
let(:role) { :developer }
before do
project.add_role(user, role)
sign_in(user)
end
def edit_and_commit(commit_changes: true, is_diff: false)
set_default_button('edit')
refresh
wait_for_requests
if is_diff
first('.js-diff-more-actions').click
click_link('Edit in single-file editor')
else
click_link('Edit')
end
fill_editor(content: 'class NextFeature\\nend\\n')
if commit_changes
click_button 'Commit changes'
end
end
def fill_editor(content: 'class NextFeature\\nend\\n')
wait_for_requests
execute_script("monaco.editor.getModels()[0].setValue('#{content}')")
end
context 'from MR diff' do
before do
visit diffs_project_merge_request_path(project, merge_request)
edit_and_commit(is_diff: true)
end
it 'returns me to the mr' do
expect(page).to have_content(merge_request.title)
end
end
it 'updates the content of file with a number as file path' do
project.repository.create_file(user, '1', 'test', message: 'testing', branch_name: branch)
visit project_blob_path(project, tree_join(branch, '1'))
edit_and_commit
expect(page).to have_content 'NextFeature'
end
it 'editing a template file in a sub directory does not change path' do
project.repository.create_file(user, 'ci/.gitlab-ci.yml', 'test', message: 'testing', branch_name: branch)
visit project_edit_blob_path(project, tree_join(branch, 'ci/.gitlab-ci.yml'))
expect(find_by_id('file_path').value).to eq('ci/.gitlab-ci.yml')
end
it 'updating file path updates syntax highlighting' do
visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
expect(find('#editor')['data-mode-id']).to eq('markdown')
find('#file_path').send_keys('foo.txt') do
expect(find('#editor')['data-mode-id']).to eq('plaintext')
end
end
context 'from blob file path' do
before do
visit project_blob_path(project, tree_join(branch, file_path))
end
it 'updates content' do
edit_and_commit
expect(page).to have_content 'successfully committed'
expect(page).to have_content 'NextFeature'
end
it 'previews content' do
edit_and_commit(commit_changes: false)
click_link 'Preview changes'
wait_for_requests
old_line_count = page.all('.line_holder.old').size
new_line_count = page.all('.line_holder.new').size
expect(old_line_count).to be > 0
expect(new_line_count).to be > 0
end
end
context 'when rendering the preview' do
it 'renders content with CommonMark' do
visit project_edit_blob_path(project, tree_join(branch, readme_file_path))
fill_editor(content: '1. one\\n - sublist\\n')
click_link 'Preview'
wait_for_requests
# the above generates two separate lists (not embedded) in CommonMark
expect(page).to have_content('sublist')
expect(page).not_to have_xpath('//ol//li//ul')
end
end
end
context 'visit blob edit' do
context 'redirects to sign in and returns' do
context 'as developer' do
let(:user) { create(:user) }
before do
project.add_developer(user)
visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'redirects to sign in and returns' do
expect(page).to have_current_path(new_user_session_path)
gitlab_sign_in(user)
expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
end
end
context 'as guest' do
let(:user) { create(:user) }
before do
visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'redirects to sign in and returns' do
expect(page).to have_current_path(new_user_session_path)
gitlab_sign_in(user)
expect(page).to have_current_path(project_blob_path(project, tree_join(branch, file_path)))
end
end
end
context 'as developer' do
let(:user) { create(:user) }
let(:protected_branch) { 'protected-branch' }
before do
project.add_developer(user)
project.repository.add_branch(user, protected_branch, 'master')
create(:protected_branch, project: project, name: protected_branch)
sign_in(user)
end
context 'on some branch' do
before do
visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'shows blob editor with same branch' do
expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
expect(find('.js-branch-name').value).to eq(branch)
end
end
context 'with protected branch' do
it 'shows blob editor with patch branch' do
freeze_time do
visit project_edit_blob_path(project, tree_join(protected_branch, file_path))
epoch = Time.zone.now.strftime('%s%L').last(5)
expect(find('.js-branch-name').value).to eq "#{user.username}-protected-branch-patch-#{epoch}"
end
end
end
end
context 'as maintainer' do
let(:user) { create(:user) }
before do
project.add_maintainer(user)
sign_in(user)
visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'shows blob editor with same branch' do
expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
expect(find('.js-branch-name').value).to eq(branch)
end
end
end
end

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Blob shortcuts', :js do
include TreeHelper
let(:project) { create(:project, :public, :repository) }
let(:path) { project.repository.ls_files(project.repository.root_ref)[0] }
let(:sha) { project.repository.commit.sha }
before do
stub_feature_flags(refactor_blob_viewer: false)
end
describe 'On a file(blob)', :js do
def get_absolute_url(path = "")
"http://#{page.server.host}:#{page.server.port}#{path}"
end
def visit_blob(fragment = nil)
visit project_blob_path(project, tree_join('master', path), anchor: fragment)
end
describe 'pressing "y"' do
it 'redirects to permalink with commit sha' do
visit_blob
wait_for_requests
find('body').native.send_key('y')
expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path))), url: true)
end
it 'maintains fragment hash when redirecting' do
fragment = "L1"
visit_blob(fragment)
wait_for_requests
find('body').native.send_key('y')
expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)), url: true)
end
end
end
end

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User creates new blob', :js do
include WebIdeSpecHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :empty_repo) }
before do
stub_feature_flags(refactor_blob_viewer: false)
end
shared_examples 'creating a file' do
it 'allows the user to add a new file in Web IDE' do
visit project_path(project)
click_link 'New file'
wait_for_requests
ide_create_new_file('dummy-file', content: "Hello world\n")
ide_commit
expect(page).to have_content('All changes are committed')
expect(project.repository.blob_at('master', 'dummy-file').data).to eql("Hello world\n")
end
end
describe 'as a maintainer' do
before do
project.add_maintainer(user)
sign_in(user)
end
it_behaves_like 'creating a file'
end
describe 'as an admin' do
let(:user) { create(:user, :admin) }
before do
sign_in(user)
gitlab_enable_admin_mode_sign_in(user)
end
it_behaves_like 'creating a file'
end
describe 'as a developer' do
before do
project.add_developer(user)
sign_in(user)
visit project_path(project)
end
it 'does not allow pushing to the default branch' do
expect(page).not_to have_content('New file')
end
end
end

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User follows pipeline suggest nudge spec when feature is enabled', :js do
include CookieHelper
let(:project) { create(:project, :empty_repo) }
let(:user) { project.first_owner }
before do
stub_feature_flags(refactor_blob_viewer: false)
end
describe 'viewing the new blob page' do
before do
sign_in(user)
end
context 'when the page is loaded from the link using the suggest_gitlab_ci_yml param' do
before do
visit namespace_project_new_blob_path(namespace_id: project.namespace, project_id: project, id: 'master', suggest_gitlab_ci_yml: 'true')
end
it 'pre-fills .gitlab-ci.yml for file name' do
file_name = page.find_by_id('file_name')
expect(file_name.value).to have_content('.gitlab-ci.yml')
end
it 'chooses the .gitlab-ci.yml Template Type' do
template_type = page.find(:css, '.template-type-selector .dropdown-toggle-text')
expect(template_type.text).to have_content('.gitlab-ci.yml')
end
it 'displays suggest_gitlab_ci_yml popover' do
page.find(:css, '.gitlab-ci-yml-selector').click
popover_selector = '.suggest-gitlab-ci-yml'
expect(page).to have_css(popover_selector, visible: true)
page.within(popover_selector) do
expect(page).to have_content('1/2: Choose a template')
end
end
it 'sets the commit cookie when the Commit button is clicked' do
click_button 'Commit changes'
expect(get_cookie("suggest_gitlab_ci_yml_commit_#{project.id}")).to be_present
end
end
context 'when the page is visited without the param' do
before do
visit namespace_project_new_blob_path(namespace_id: project.namespace, project_id: project, id: 'master')
end
it 'does not pre-fill .gitlab-ci.yml for file name' do
file_name = page.find_by_id('file_name')
expect(file_name.value).not_to have_content('.gitlab-ci.yml')
end
it 'does not choose the .gitlab-ci.yml Template Type' do
template_type = page.find(:css, '.template-type-selector .dropdown-toggle-text')
expect(template_type.text).to have_content('Select a template type')
end
it 'does not display suggest_gitlab_ci_yml popover' do
popover_selector = '.b-popover.suggest-gitlab-ci-yml'
expect(page).not_to have_css(popover_selector, visible: true)
end
end
end
end

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User views pipeline editor button on root ci config file', :js do
include BlobSpecHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
before do
stub_feature_flags(refactor_blob_viewer: false)
end
context "when the ci config is the root file" do
before do
project.add_developer(user)
sign_in(user)
end
it 'shows the button to the Pipeline Editor' do
project.update!(ci_config_path: '.my-config.yml')
project.repository.create_file(user, project.ci_config_path_or_default, 'test', message: 'testing', branch_name: 'master')
visit project_blob_path(project, File.join('master', '.my-config.yml'))
expect(page).to have_content('Edit in pipeline editor')
end
it 'does not shows the Pipeline Editor button' do
project.repository.create_file(user, '.my-sub-config.yml', 'test', message: 'testing', branch_name: 'master')
visit project_blob_path(project, File.join('master', '.my-sub-config.yml'))
expect(page).not_to have_content('Edit in pipeline editor')
end
end
context "when user cannot collaborate" do
before do
sign_in(user)
end
it 'does not shows the Pipeline Editor button' do
visit project_blob_path(project, File.join('master', '.my-config.yml'))
expect(page).not_to have_content('Edit in pipeline editor')
end
end
end

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > User wants to edit a file' do
let(:project) { create(:project, :repository) }
let(:user) { project.first_owner }
let(:commit_params) do
{
start_branch: project.default_branch,
branch_name: project.default_branch,
commit_message: "Committing First Update",
file_path: ".gitignore",
file_content: "First Update",
last_commit_sha: Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch,
".gitignore").sha
}
end
before do
stub_feature_flags(refactor_blob_viewer: false)
sign_in user
visit project_edit_blob_path(project,
File.join(project.default_branch, '.gitignore'))
end
it 'file has been updated since the user opened the edit page' do
Files::UpdateService.new(project, user, commit_params).execute
click_button 'Commit changes'
expect(page).to have_content 'Someone edited the file the same time you did.'
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > Find file keyboard shortcuts', :js do
let(:project) { create(:project, :repository) }
let(:user) { project.first_owner }
before do
stub_feature_flags(refactor_blob_viewer: false)
sign_in user
visit project_find_file_path(project, project.repository.root_ref)
wait_for_requests
end
it 'opens file when pressing enter key' do
fill_in 'file_find', with: 'CHANGELOG'
find('#file_find').native.send_keys(:enter)
expect(page).to have_selector('.blob-content-holder')
page.within('.js-file-title') do
expect(page).to have_content('CHANGELOG')
end
end
it 'navigates files with arrow keys' do
fill_in 'file_find', with: 'application.'
find('#file_find').native.send_keys(:down)
find('#file_find').native.send_keys(:enter)
expect(page).to have_selector('.blob-content-holder')
page.within('.js-file-title') do
expect(page).to have_content('application.js')
end
end
end

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > Project owner creates a license file', :js do
let(:project) { create(:project, :repository) }
let(:project_maintainer) { project.first_owner }
before do
stub_feature_flags(refactor_blob_viewer: false)
project.repository.delete_file(project_maintainer, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
sign_in(project_maintainer)
visit project_path(project)
end
it 'project maintainer creates a license file manually from a template' do
visit project_tree_path(project, project.repository.root_ref)
find('.add-to-tree').click
click_link 'New file'
fill_in :file_name, with: 'LICENSE'
expect(page).to have_selector('.license-selector')
select_template('MIT License')
file_content = first('.file-editor')
expect(file_content).to have_content('MIT License')
expect(file_content).to have_content("Copyright (c) #{Time.zone.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
click_button 'Commit changes'
expect(page).to have_current_path(
project_blob_path(project, 'master/LICENSE'), ignore_query: true)
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.zone.now.year} #{project.namespace.human_name}")
end
it 'project maintainer creates a license file from the "Add license" link' do
click_link 'Add LICENSE'
expect(page).to have_content('New file')
expect(page).to have_current_path(
project_new_blob_path(project, 'master'), ignore_query: true)
expect(find('#file_name').value).to eq('LICENSE')
expect(page).to have_selector('.license-selector')
select_template('MIT License')
file_content = first('.file-editor')
expect(file_content).to have_content('MIT License')
expect(file_content).to have_content("Copyright (c) #{Time.zone.now.year} #{project.namespace.human_name}")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true
click_button 'Commit changes'
expect(page).to have_current_path(
project_blob_path(project, 'master/LICENSE'), ignore_query: true)
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.zone.now.year} #{project.namespace.human_name}")
end
def select_template(template)
page.within('.js-license-selector-wrap') do
click_button 'Apply a template'
click_link template
wait_for_requests
end
end
end

View File

@ -0,0 +1,377 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe "User browses files", :js do
include RepoHelpers
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
let(:project) { create(:project, :repository, name: "Shop") }
let(:project2) { create(:project, :repository, name: "Another Project", path: "another-project") }
let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
let(:user) { project.first_owner }
before do
stub_feature_flags(refactor_blob_viewer: false)
sign_in(user)
end
it "shows last commit for current directory", :js do
visit(tree_path_root_ref)
click_link("files")
last_commit = project.repository.last_commit_for_path(project.default_branch, "files")
page.within(".commit-detail") do
expect(page).to have_content(last_commit.short_id).and have_content(last_commit.author_name)
end
end
context "when browsing the master branch", :js do
before do
visit(tree_path_root_ref)
end
it "shows files from a repository" do
expect(page).to have_content("VERSION")
.and have_content(".gitignore")
.and have_content("LICENSE")
end
it "shows the `Browse Directory` link" do
click_link("files")
page.within('.repo-breadcrumb') do
expect(page).to have_link('files')
end
click_link("History")
expect(page).to have_link("Browse Directory").and have_no_link("Browse Code")
end
it "shows the `Browse File` link" do
page.within(".tree-table") do
click_link("README.md")
end
click_link("History")
expect(page).to have_link("Browse File").and have_no_link("Browse Files")
end
it "shows the `Browse Files` link" do
click_link("History")
expect(page).to have_link("Browse Files").and have_no_link("Browse Directory")
end
it "redirects to the permalink URL" do
click_link(".gitignore")
click_link("Permalink")
permalink_path = project_blob_path(project, "#{project.repository.commit.sha}/.gitignore")
expect(page).to have_current_path(permalink_path, ignore_query: true)
end
end
context "when browsing the `markdown` branch", :js do
context "when browsing the root" do
before do
visit(project_tree_path(project, "markdown"))
end
it "shows correct files and links" do
expect(page).to have_current_path(project_tree_path(project, "markdown"), ignore_query: true)
expect(page).to have_content("README.md")
.and have_content("CHANGELOG")
.and have_content("Welcome to GitLab GitLab is a free project and repository management application")
.and have_link("GitLab API doc")
.and have_link("GitLab API website")
.and have_link("Rake tasks")
.and have_link("backup and restore procedure")
.and have_link("GitLab API doc directory")
.and have_link("Maintenance")
.and have_header_with_correct_id_and_link(2, "Application details", "application-details")
.and have_link("empty", href: "")
.and have_link("#id", href: "#id")
.and have_link("/#id", href: project_blob_path(project, "markdown/README.md", anchor: "id"))
.and have_link("README.md#id", href: project_blob_path(project, "markdown/README.md", anchor: "id"))
.and have_link("d/README.md#id", href: project_blob_path(project, "markdown/db/README.md", anchor: "id"))
end
it "shows correct content of file" do
click_link("GitLab API doc")
expect(page).to have_current_path(project_blob_path(project, "markdown/doc/api/README.md"), ignore_query: true)
expect(page).to have_content("All API requests require authentication")
.and have_content("Contents")
.and have_link("Users")
.and have_link("Rake tasks")
.and have_header_with_correct_id_and_link(1, "GitLab API", "gitlab-api")
click_link("Users")
expect(page).to have_current_path(project_blob_path(project, "markdown/doc/api/users.md"), ignore_query: true)
expect(page).to have_content("Get a list of users.")
page.go_back
click_link("Rake tasks")
expect(page).to have_current_path(project_tree_path(project, "markdown/doc/raketasks"), ignore_query: true)
expect(page).to have_content("backup_restore.md").and have_content("maintenance.md")
click_link("maintenance.md")
expect(page).to have_current_path(project_blob_path(project, "markdown/doc/raketasks/maintenance.md"), ignore_query: true)
expect(page).to have_content("bundle exec rake gitlab:env:info RAILS_ENV=production")
click_link("shop")
page.within(".tree-table") do
click_link("README.md")
end
page.go_back
page.within(".tree-table") do
click_link("d")
end
expect(page).to have_link("..", href: project_tree_path(project, "markdown/"))
page.within(".tree-table") do
click_link("README.md")
end
expect(page).to have_link("empty", href: "")
end
it "shows correct content of directory" do
click_link("GitLab API doc directory")
expect(page).to have_current_path(project_tree_path(project, "markdown/doc/api"), ignore_query: true)
expect(page).to have_content("README.md").and have_content("users.md")
click_link("Users")
expect(page).to have_current_path(project_blob_path(project, "markdown/doc/api/users.md"), ignore_query: true)
expect(page).to have_content("List users").and have_content("Get a list of users.")
end
end
end
context 'when commit message has markdown', :js do
before do
project.repository.create_file(user, 'index', 'test', message: ':star: testing', branch_name: 'master')
visit(project_tree_path(project, "master"))
end
it 'renders emojis' do
expect(page).to have_selector('gl-emoji', count: 2)
end
end
context "when browsing a `improve/awesome` branch", :js do
before do
visit(project_tree_path(project, "improve/awesome"))
end
it "shows files from a repository" do
expect(page).to have_content("VERSION")
.and have_content(".gitignore")
.and have_content("LICENSE")
click_link("files")
page.within('.repo-breadcrumb') do
expect(page).to have_link('files')
end
click_link("html")
page.within('.repo-breadcrumb') do
expect(page).to have_link('html')
end
expect(page).to have_link('500.html')
end
end
context "when browsing a `Ääh-test-utf-8` branch", :js do
before do
project.repository.create_branch('Ääh-test-utf-8', project.repository.root_ref)
visit(project_tree_path(project, "Ääh-test-utf-8"))
end
it "shows files from a repository" do
expect(page).to have_content("VERSION")
.and have_content(".gitignore")
.and have_content("LICENSE")
click_link("files")
page.within('.repo-breadcrumb') do
expect(page).to have_link('files')
end
click_link("html")
page.within('.repo-breadcrumb') do
expect(page).to have_link('html')
end
expect(page).to have_link('500.html')
end
end
context "when browsing a `test-#` branch", :js do
before do
project.repository.create_branch('test-#', project.repository.root_ref)
visit(project_tree_path(project, "test-#"))
end
it "shows files from a repository" do
expect(page).to have_content("VERSION")
.and have_content(".gitignore")
.and have_content("LICENSE")
click_link("files")
page.within('.repo-breadcrumb') do
expect(page).to have_link('files')
end
click_link("html")
page.within('.repo-breadcrumb') do
expect(page).to have_link('html')
end
expect(page).to have_link('500.html')
end
end
context "when browsing a specific ref", :js do
let(:ref) { project_tree_path(project, "6d39438") }
before do
visit(ref)
end
it "shows files from a repository for `6d39438`" do
expect(page).to have_current_path(ref, ignore_query: true)
expect(page).to have_content(".gitignore").and have_content("LICENSE")
end
it "shows files from a repository with apostroph in its name" do
first(".js-project-refs-dropdown").click
page.within(".project-refs-form") do
click_link("'test'")
end
expect(page).to have_selector(".dropdown-toggle-text", text: "'test'")
visit(project_tree_path(project, "'test'"))
expect(page).not_to have_selector(".tree-commit .animation-container")
end
it "shows the code with a leading dot in the directory" do
first(".js-project-refs-dropdown").click
page.within(".project-refs-form") do
click_link("fix")
end
visit(project_tree_path(project, "fix/.testdir"))
expect(page).not_to have_selector(".tree-commit .animation-container")
end
it "does not show the permalink link" do
click_link(".gitignore")
expect(page).not_to have_link("permalink")
end
end
context "when browsing a file content", :js do
before do
visit(tree_path_root_ref)
wait_for_requests
click_link(".gitignore")
end
it "shows a file content" do
expect(page).to have_content("*.rbc")
end
it "is possible to blame" do
click_link("Blame")
expect(page).to have_content("*.rb")
.and have_content("Dmitriy Zaporozhets")
.and have_content("Initial commit")
.and have_content("Ignore DS files")
previous_commit_anchor = "//a[@title='Ignore DS files']/parent::span/following-sibling::span/a"
find(:xpath, previous_commit_anchor).click
expect(page).to have_content("*.rb")
.and have_content("Dmitriy Zaporozhets")
.and have_content("Initial commit")
expect(page).not_to have_content("Ignore DS files")
end
end
context "when browsing a file with pathspec characters" do
let(:filename) { ':wq' }
let(:newrev) { project.repository.commit('master').sha }
before do
create_file_in_repo(project, 'master', 'master', filename, 'Test file')
path = File.join('master', filename)
visit(project_blob_path(project, path))
wait_for_requests
end
it "shows raw file content in a new tab" do
new_tab = window_opened_by {click_link 'Open raw'}
within_window new_tab do
expect(page).to have_content("Test file")
end
end
end
context "when browsing a raw file" do
before do
visit(tree_path_root_ref)
wait_for_requests
click_link(".gitignore")
wait_for_requests
end
it "shows raw file content in a new tab" do
new_tab = window_opened_by {click_link 'Open raw'}
within_window new_tab do
expect(page).to have_content("*.rbc")
end
end
end
end

View File

@ -0,0 +1,86 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > User browses LFS files' do
let(:project) { create(:project, :repository) }
let(:user) { project.first_owner }
before do
stub_feature_flags(refactor_blob_viewer: false)
sign_in(user)
end
context 'when LFS is disabled', :js do
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:lfs_enabled?).and_return(false)
end
visit project_tree_path(project, 'lfs')
wait_for_requests
end
it 'is possible to see raw content of LFS pointer' do
click_link 'files'
page.within('.repo-breadcrumb') do
expect(page).to have_link('files')
end
click_link 'lfs'
page.within('.repo-breadcrumb') do
expect(page).to have_link('lfs')
end
click_link 'lfs_object.iso'
expect(page).to have_content 'version https://git-lfs.github.com/spec/v1'
expect(page).to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
expect(page).to have_content 'size 1575078'
expect(page).not_to have_content 'Download (1.5 MB)'
end
end
context 'when LFS is enabled', :js do
before do
allow_next_found_instance_of(Project) do |project|
allow(project).to receive(:lfs_enabled?).and_return(true)
end
visit project_tree_path(project, 'lfs')
wait_for_requests
end
it 'shows an LFS object' do
click_link('files')
page.within('.repo-breadcrumb') do
expect(page).to have_link('files')
end
click_link('lfs')
click_link('lfs_object.iso')
expect(page).to have_content('Download (1.5 MB)')
expect(page).not_to have_content('version https://git-lfs.github.com/spec/v1')
expect(page).not_to have_content('oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897')
expect(page).not_to have_content('size 1575078')
page.within('.content') do
expect(page).to have_content('Delete')
expect(page).to have_content('History')
expect(page).to have_content('Permalink')
expect(page).to have_content('Replace')
expect(page).to have_link('Download')
expect(page).not_to have_content('Annotate')
expect(page).not_to have_content('Blame')
expect(page).not_to have_selector(:link_or_button, text: /^Edit$/)
expect(page).to have_selector(:link_or_button, 'Open in Web IDE')
end
end
end
end

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > User deletes files', :js do
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
let(:user) { create(:user) }
before do
stub_feature_flags(refactor_blob_viewer: false)
sign_in(user)
end
context 'when an user has write access' do
before do
project.add_maintainer(user)
visit(project_tree_path_root_ref)
wait_for_requests
end
it 'deletes the file', :js do
click_link('.gitignore')
expect(page).to have_content('.gitignore')
click_on('Delete')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Delete file')
expect(page).to have_current_path(project_tree_path(project, 'master/'), ignore_query: true)
expect(page).not_to have_content('.gitignore')
end
end
context 'when an user does not have write access', :js do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
wait_for_requests
end
it 'deletes the file in a forked project', :js, :sidekiq_might_not_need_inline do
click_link('.gitignore')
expect(page).to have_content('.gitignore')
click_on('Delete')
expect(page).to have_link('Fork')
expect(page).to have_button('Cancel')
click_link('Fork')
expect(page).to have_content(fork_message)
click_on('Delete')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Delete file')
fork = user.fork_of(project2.reload)
expect(page).to have_current_path(project_new_merge_request_path(fork), ignore_query: true)
expect(page).to have_content('New commit message')
end
end
end

View File

@ -0,0 +1,226 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > User edits files', :js do
include ProjectForksHelper
include BlobSpecHelpers
let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
let(:user) { create(:user) }
before do
stub_feature_flags(refactor_blob_viewer: false)
sign_in(user)
end
after do
unset_default_button
end
shared_examples 'unavailable for an archived project' do
it 'does not show the edit link for an archived project', :js do
project.update!(archived: true)
visit project_tree_path(project, project.repository.root_ref)
click_link('.gitignore')
aggregate_failures 'available edit buttons' do
expect(page).not_to have_text('Edit')
expect(page).not_to have_text('Web IDE')
expect(page).not_to have_text('Replace')
expect(page).not_to have_text('Delete')
end
end
end
context 'when an user has write access', :js do
before do
project.add_maintainer(user)
visit(project_tree_path_root_ref)
wait_for_requests
end
it 'inserts a content of a file' do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
expect(editor_value).to eq('*.rbca')
end
it 'does not show the edit link if a file is binary' do
binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png')
visit(project_blob_path(project, binary_file))
wait_for_requests
page.within '.content' do
expect(page).not_to have_link('edit')
end
end
it 'commits an edited file' do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
expect(page).to have_current_path(project_blob_path(project, 'master/.gitignore'), ignore_query: true)
wait_for_requests
expect(page).to have_content('*.rbca')
end
it 'commits an edited file to a new branch' do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
fill_in(:commit_message, with: 'New commit message', visible: true)
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Commit changes')
expect(page).to have_current_path(project_new_merge_request_path(project), ignore_query: true)
click_link('Changes')
expect(page).to have_content('*.rbca')
end
it 'shows the diff of an edited file' do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
click_link('Preview changes')
expect(page).to have_css('.line_holder.new')
end
it_behaves_like 'unavailable for an archived project'
end
context 'when an user does not have write access', :js do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
wait_for_requests
end
def expect_fork_prompt
expect(page).to have_selector(:link_or_button, 'Fork')
expect(page).to have_selector(:link_or_button, 'Cancel')
expect(page).to have_content(
"You cant edit files directly in this project. "\
"Fork this project and submit a merge request with your changes."
)
end
def expect_fork_status
expect(page).to have_content(
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
)
end
it 'inserts a content of a file in a forked project', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
expect_fork_prompt
click_link_or_button('Fork project')
expect_fork_status
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
expect(editor_value).to eq('*.rbca')
end
it 'commits an edited file in a forked project', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
expect_fork_prompt
click_link_or_button('Fork project')
find('.file-editor', match: :first)
find('#editor')
set_editor_value('*.rbca')
fill_in(:commit_message, with: 'New commit message', visible: true)
click_button('Commit changes')
fork = user.fork_of(project2.reload)
expect(page).to have_current_path(project_new_merge_request_path(fork), ignore_query: true)
wait_for_requests
expect(page).to have_content('New commit message')
end
context 'when the user already had a fork of the project', :js do
let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) }
before do
visit(project2_tree_path_root_ref)
wait_for_requests
end
it 'links to the forked project for editing', :sidekiq_might_not_need_inline do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
expect(page).not_to have_link('Fork project')
find('#editor')
set_editor_value('*.rbca')
fill_in(:commit_message, with: 'Another commit', visible: true)
click_button('Commit changes')
fork = user.fork_of(project2)
expect(page).to have_current_path(project_new_merge_request_path(fork), ignore_query: true)
wait_for_requests
expect(page).to have_content('Another commit')
expect(page).to have_content("From #{forked_project.full_path}")
expect(page).to have_content("into #{project2.full_path}")
end
it_behaves_like 'unavailable for an archived project' do
let(:project) { project2 }
end
end
end
end

View File

@ -0,0 +1,93 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Projects > Files > User replaces files', :js do
include DropzoneHelper
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
let(:project) { create(:project, :repository, name: 'Shop') }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
let(:user) { create(:user) }
before do
stub_feature_flags(refactor_blob_viewer: false)
sign_in(user)
end
context 'when an user has write access' do
before do
project.add_maintainer(user)
visit(project_tree_path_root_ref)
wait_for_requests
end
it 'replaces an existed file with a new one' do
click_link('.gitignore')
expect(page).to have_content('.gitignore')
click_on('Replace')
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'Replacement file commit message')
end
click_button('Replace file')
expect(page).to have_content('Lorem ipsum dolor sit amet')
expect(page).to have_content('Sed ut perspiciatis unde omnis')
expect(page).to have_content('Replacement file commit message')
end
end
context 'when an user does not have write access' do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
wait_for_requests
end
it 'replaces an existed file with a new one in a forked project', :sidekiq_might_not_need_inline do
click_link('.gitignore')
expect(page).to have_content('.gitignore')
click_on('Replace')
expect(page).to have_link('Fork')
expect(page).to have_button('Cancel')
click_link('Fork')
expect(page).to have_content(fork_message)
click_on('Replace')
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'Replacement file commit message')
end
click_button('Replace file')
expect(page).to have_content('Replacement file commit message')
fork = user.fork_of(project2.reload)
expect(page).to have_current_path(project_new_merge_request_path(fork), ignore_query: true)
click_link('Changes')
expect(page).to have_content('Lorem ipsum dolor sit amet')
expect(page).to have_content('Sed ut perspiciatis unde omnis')
end
end
end

View File

@ -1,7 +1,19 @@
import '~/commons';
import { shallowMount } from '@vue/test-utils';
import { GlButton, GlSprintf } from '@gitlab/ui';
import { sprintf } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { stubExperiments } from 'helpers/experimentation_helper';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import PipelinesCiTemplate from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
import {
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
} from '~/pipeline_editor/constants';
const pipelineEditorPath = '/-/ci/editor';
const suggestedCiTemplates = [
@ -10,16 +22,20 @@ const suggestedCiTemplates = [
{ name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
];
jest.mock('~/experimentation/experiment_tracking');
describe('Pipelines CI Templates', () => {
let wrapper;
let trackingSpy;
const createWrapper = () => {
return shallowMount(PipelinesCiTemplate, {
const createWrapper = (propsData = {}, stubs = {}) => {
return shallowMountExtended(PipelinesCiTemplate, {
provide: {
pipelineEditorPath,
suggestedCiTemplates,
},
propsData,
stubs,
});
};
@ -28,6 +44,9 @@ describe('Pipelines CI Templates', () => {
const findTemplateLinks = () => wrapper.findAll('[data-testid="template-link"]');
const findTemplateNames = () => wrapper.findAll('[data-testid="template-name"]');
const findTemplateLogos = () => wrapper.findAll('[data-testid="template-logo"]');
const findSettingsLink = () => wrapper.findByTestId('settings-link');
const findDocumentationLink = () => wrapper.findByTestId('documentation-link');
const findSettingsButton = () => wrapper.findByTestId('settings-button');
afterEach(() => {
wrapper.destroy();
@ -69,7 +88,7 @@ describe('Pipelines CI Templates', () => {
it('has the description of the template', () => {
expect(findTemplateDescriptions().at(0).text()).toBe(
'CI/CD template to test and deploy your Android project.',
sprintf(I18N.templates.description, { name: 'Android' }),
);
});
@ -104,4 +123,73 @@ describe('Pipelines CI Templates', () => {
});
});
});
describe('when the runners_availability_section experiment is active', () => {
beforeEach(() => {
stubExperiments({ runners_availability_section: 'candidate' });
});
describe('when runners are available', () => {
beforeEach(() => {
wrapper = createWrapper({ anyRunnersAvailable: true }, { GitlabExperiment, GlSprintf });
});
it('renders the templates', () => {
expect(findTestTemplateLinks().exists()).toBe(true);
expect(findTemplateLinks().exists()).toBe(true);
});
it('show the runners available section', () => {
expect(wrapper.text()).toContain(I18N.runners.title);
});
it('tracks an event when clicking the settings link', () => {
findSettingsLink().vm.$emit('click');
expect(ExperimentTracking).toHaveBeenCalledWith(
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
);
});
it('tracks an event when clicking the documentation link', () => {
findDocumentationLink().vm.$emit('click');
expect(ExperimentTracking).toHaveBeenCalledWith(
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
);
});
});
describe('when runners are not available', () => {
beforeEach(() => {
wrapper = createWrapper({ anyRunnersAvailable: false }, { GitlabExperiment, GlButton });
});
it('does not render the templates', () => {
expect(findTestTemplateLinks().exists()).toBe(false);
expect(findTemplateLinks().exists()).toBe(false);
});
it('show the no runners available section', () => {
expect(wrapper.text()).toContain(I18N.noRunners.title);
});
it('tracks an event when clicking the settings button', () => {
findSettingsButton().trigger('click');
expect(ExperimentTracking).toHaveBeenCalledWith(
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
);
});
});
});
});

View File

@ -93,4 +93,63 @@ RSpec.describe Ci::PipelinesHelper do
end
end
end
describe '#pipelines_list_data' do
let_it_be(:project) { create(:project) }
subject(:data) { helper.pipelines_list_data(project, 'list_url') }
before do
allow(helper).to receive(:can?).and_return(true)
end
it 'has the expected keys' do
expect(subject.keys).to match_array([:endpoint,
:project_id,
:params,
:artifacts_endpoint,
:artifacts_endpoint_placeholder,
:pipeline_schedule_url,
:empty_state_svg_path,
:error_state_svg_path,
:no_pipelines_svg_path,
:can_create_pipeline,
:new_pipeline_path,
:ci_lint_path,
:reset_cache_path,
:has_gitlab_ci,
:pipeline_editor_path,
:suggested_ci_templates,
:code_quality_page_path,
:ci_runner_settings_path])
end
describe 'the `any_runners_available` attribute' do
subject { data[:any_runners_available] }
context 'when the `runners_availability_section` experiment variant is control' do
before do
stub_experiments(runners_availability_section: :control)
end
it { is_expected.to be_nil }
end
context 'when the `runners_availability_section` experiment variant is candidate' do
before do
stub_experiments(runners_availability_section: :candidate)
end
context 'when there are no runners' do
it { is_expected.to eq('false') }
end
context 'when there are runners' do
let!(:runner) { create(:ci_runner, :project, projects: [project]) }
it { is_expected.to eq('true') }
end
end
end
end
end

View File

@ -112,11 +112,18 @@ RSpec.describe Gitlab do
expect(described_class.com?).to eq false
end
it 'is true when GITLAB_SIMULATE_SAAS is true' do
it 'is true when GITLAB_SIMULATE_SAAS is true and in development' do
stub_rails_env('development')
stub_env('GITLAB_SIMULATE_SAAS', '1')
expect(described_class.com?).to eq true
end
it 'is false when GITLAB_SIMULATE_SAAS is true and in test' do
stub_env('GITLAB_SIMULATE_SAAS', '1')
expect(described_class.com?).to eq false
end
end
describe '.com' do
@ -239,8 +246,8 @@ RSpec.describe Gitlab do
stub_env('GITLAB_SIMULATE_SAAS', '1')
end
it 'is true when test env' do
expect(subject).to eq true
it 'is false when test env' do
expect(subject).to eq false
end
it 'is true when dev env' do
@ -249,7 +256,7 @@ RSpec.describe Gitlab do
expect(subject).to eq true
end
it 'is false when env is not dev or test' do
it 'is false when env is not dev' do
stub_rails_env('production')
expect(subject).to eq false