Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3e1ab18831
commit
b6927b3dd6
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
|
@ -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).
|
||||
|
|
|
@ -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**.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 can’t 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
|
|
@ -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
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue