Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-10-30 00:07:52 +00:00
parent cb3e6b9c1b
commit 4c016ad024
31 changed files with 600 additions and 48 deletions

View file

@ -0,0 +1,82 @@
<script>
import { GlButton, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { mapState, mapActions } from 'vuex';
export default {
components: {
GlButton,
GlFormGroup,
GlFormInput,
GlLink,
Icon,
},
data() {
return { placeholderUrl: 'https://my-url.grafana.net/my-dashboard' };
},
computed: {
...mapState(['operationsSettingsEndpoint', 'grafanaToken', 'grafanaUrl']),
localGrafanaToken: {
get() {
return this.grafanaToken;
},
set(token) {
this.setGrafanaToken(token);
},
},
localGrafanaUrl: {
get() {
return this.grafanaUrl;
},
set(url) {
this.setGrafanaUrl(url);
},
},
},
methods: {
...mapActions(['setGrafanaUrl', 'setGrafanaToken', 'updateGrafanaIntegration']),
},
};
</script>
<template>
<section id="grafana" class="settings no-animate js-grafana-integration">
<div class="settings-header">
<h4 class="js-section-header">
{{ s__('GrafanaIntegration|Grafana Authentication') }}
</h4>
<gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button>
<p class="js-section-sub-header">
{{ s__('GrafanaIntegration|Embed Grafana charts in GitLab issues.') }}
</p>
</div>
<div class="settings-content">
<form>
<gl-form-group
:label="s__('GrafanaIntegration|Grafana URL')"
label-for="grafana-url"
:description="s__('GrafanaIntegration|Enter the base URL of the Grafana instance.')"
>
<gl-form-input id="grafana-url" v-model="localGrafanaUrl" :placeholder="placeholderUrl" />
</gl-form-group>
<gl-form-group :label="s__('GrafanaIntegration|API Token')" label-for="grafana-token">
<gl-form-input id="grafana-token" v-model="localGrafanaToken" />
<p class="form-text text-muted">
{{ s__('GrafanaIntegration|Enter the Grafana API Token.') }}
<a
href="https://grafana.com/docs/http_api/auth/#create-api-token"
target="_blank"
rel="noopener noreferrer"
>
{{ __('More information') }}
<icon name="external-link" class="vertical-align-middle" />
</a>
</p>
</gl-form-group>
<gl-button variant="success" @click="updateGrafanaIntegration">
{{ __('Save Changes') }}
</gl-button>
</form>
</div>
</section>
</template>

View file

@ -0,0 +1,15 @@
import Vue from 'vue';
import store from './store';
import GrafanaIntegration from './components/grafana_integration.vue';
export default () => {
const el = document.querySelector('.js-grafana-integration');
return new Vue({
el,
store: store(el.dataset),
render(createElement) {
return createElement(GrafanaIntegration);
},
});
};

View file

@ -0,0 +1,38 @@
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import createFlash from '~/flash';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import * as mutationTypes from './mutation_types';
export const setGrafanaUrl = ({ commit }, url) => commit(mutationTypes.SET_GRAFANA_URL, url);
export const setGrafanaToken = ({ commit }, token) =>
commit(mutationTypes.SET_GRAFANA_TOKEN, token);
export const updateGrafanaIntegration = ({ state, dispatch }) =>
axios
.patch(state.operationsSettingsEndpoint, {
project: {
grafana_integration_attributes: {
grafana_url: state.grafanaUrl,
token: state.grafanaToken,
},
},
})
.then(() => dispatch('receiveGrafanaIntegrationUpdateSuccess'))
.catch(error => dispatch('receiveGrafanaIntegrationUpdateError', error));
export const receiveGrafanaIntegrationUpdateSuccess = () => {
/**
* The operations_controller currently handles successful requests
* by creating a flash banner messsage to notify the user.
*/
refreshCurrentPage();
};
export const receiveGrafanaIntegrationUpdateError = (_, error) => {
const { response } = error;
const message = response.data && response.data.message ? response.data.message : '';
createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert');
};

View file

@ -0,0 +1,16 @@
import Vue from 'vue';
import Vuex from 'vuex';
import createState from './state';
import * as actions from './actions';
import mutations from './mutations';
Vue.use(Vuex);
export const createStore = initialState =>
new Vuex.Store({
state: createState(initialState),
actions,
mutations,
});
export default createStore;

View file

@ -0,0 +1,2 @@
export const SET_GRAFANA_URL = 'SET_GRAFANA_URL';
export const SET_GRAFANA_TOKEN = 'SET_GRAFANA_TOKEN';

View file

@ -0,0 +1,10 @@
import * as types from './mutation_types';
export default {
[types.SET_GRAFANA_URL](state, url) {
state.grafanaUrl = url;
},
[types.SET_GRAFANA_TOKEN](state, token) {
state.grafanaToken = token;
},
};

View file

@ -0,0 +1,5 @@
export default (initialState = {}) => ({
operationsSettingsEndpoint: initialState.operationsSettingsEndpoint,
grafanaToken: initialState.grafanaIntegrationToken || '',
grafanaUrl: initialState.grafanaIntegrationUrl || '',
});

View file

@ -1,9 +1,13 @@
import mountErrorTrackingForm from '~/error_tracking_settings';
import mountOperationSettings from '~/operation_settings';
import mountGrafanaIntegration from '~/grafana_integration';
import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm();
mountOperationSettings();
if (gon.features.gfmGrafanaIntegration) {
mountGrafanaIntegration();
}
initSettingsPanels();
});

View file

@ -20,12 +20,17 @@ document.addEventListener('DOMContentLoaded', () => {
// Save the URL fragment from the current window location. This will be present if the user was
// redirected to sign-in after attempting to access a protected URL that included a fragment.
preserveUrlFragment(window.location.hash);
});
export default function trackData() {
if (gon.tracking_data) {
const tab = document.querySelector(".new-session-tabs a[href='#register-pane']");
const { category, action, ...data } = gon.tracking_data;
tab.addEventListener('click', () => {
Tracking.event(category, action, data);
});
}
});
}
trackData();

View file

@ -10,6 +10,14 @@ module Analytics
alias_attribute :parent, :project
alias_attribute :parent_id, :project_id
def self.relative_positioning_query_base(stage)
where(project_id: stage.project_id)
end
def self.relative_positioning_parent_column
:project_id
end
end
end
end

View file

@ -4,6 +4,7 @@ module Analytics
module CycleAnalytics
module Stage
extend ActiveSupport::Concern
include RelativePositioning
included do
validates :name, presence: true
@ -17,6 +18,7 @@ module Analytics
alias_attribute :custom_stage?, :custom
scope :default_stages, -> { where(custom: false) }
scope :ordered, -> { order(:relative_position, :id) }
end
def parent=(_)
@ -58,6 +60,10 @@ module Analytics
end_event_identifier.to_s.eql?(stage_params[:end_event_identifier].to_s)
end
def find_with_same_parent!(id)
parent.cycle_analytics_stages.find(id)
end
private
def validate_stage_event_pairs

View file

@ -16,13 +16,19 @@
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
= _('Overview')
- if @group.subgroup?
= _('Subgroup overview')
- else
= _('Group overview')
%ul.sidebar-sub-level-items
= nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
= link_to group_path(@group) do
%strong.fly-out-top-item-name
= _('Overview')
- if @group.subgroup?
= _('Subgroup overview')
- else
= _('Group overview')
%li.divider.fly-out-top-item
= nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do

View file

@ -13,13 +13,13 @@
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
= _('Project')
= _('Project overview')
%ul.sidebar-sub-level-items
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
= link_to project_path(@project) do
%strong.fly-out-top-item-name
= _('Project')
= _('Project overview')
%li.divider.fly-out-top-item
= nav_link(path: 'projects#show') do
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do

View file

@ -13,7 +13,7 @@
%p= s_('Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
- else
%p
= s_('Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha}
= s_('Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha, environment_name: @environment.name}
.modal-footer
= button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-danger' do

View file

@ -0,0 +1,2 @@
.js-grafana-integration{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
grafana_integration: { url: grafana_integration_url, token: grafana_integration_token } } }

View file

@ -5,4 +5,5 @@
= render_if_exists 'projects/settings/operations/incidents'
= render 'projects/settings/operations/error_tracking'
= render 'projects/settings/operations/external_dashboard'
= render 'projects/settings/operations/grafana_integration'
= render_if_exists 'projects/settings/operations/tracing'

View file

@ -0,0 +1,5 @@
---
title: Fix environment name in rollback dialog
merge_request: 19209
author:
type: fixed

View file

@ -353,7 +353,7 @@ configuration.
NOTE: **Note:** Enabling a storage driver other than `filesystem` would mean
that your Docker client needs to be able to access the storage backend directly.
In that case, you must use an address that resolves and is accessible outside GitLab server.
In that case, you must use an address that resolves and is accessible outside GitLab server. The Docker client will continue to authenticate via GitLab but data transfer will be direct to and from the storage backend.
The different supported drivers are:

View file

@ -6339,10 +6339,10 @@ msgstr ""
msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
msgstr ""
msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgid "Environments|This action will run the job defined by %{environment_name} for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
msgid "Environments|Updated"
@ -8210,6 +8210,24 @@ msgstr ""
msgid "Grafana URL"
msgstr ""
msgid "GrafanaIntegration|API Token"
msgstr ""
msgid "GrafanaIntegration|Embed Grafana charts in GitLab issues."
msgstr ""
msgid "GrafanaIntegration|Enter the Grafana API Token."
msgstr ""
msgid "GrafanaIntegration|Enter the base URL of the Grafana instance."
msgstr ""
msgid "GrafanaIntegration|Grafana Authentication"
msgstr ""
msgid "GrafanaIntegration|Grafana URL"
msgstr ""
msgid "Grant access"
msgstr ""
@ -8273,6 +8291,9 @@ msgstr ""
msgid "Group name"
msgstr ""
msgid "Group overview"
msgstr ""
msgid "Group overview content"
msgstr ""
@ -12737,6 +12758,9 @@ msgstr ""
msgid "Project name"
msgstr ""
msgid "Project overview"
msgstr ""
msgid "Project slug"
msgstr ""
@ -15847,6 +15871,9 @@ msgstr ""
msgid "StorageSize|Unknown"
msgstr ""
msgid "Subgroup overview"
msgstr ""
msgid "SubgroupCreationLevel|Allowed to create subgroups"
msgstr ""

View file

@ -3,7 +3,8 @@
require 'pathname'
module QA
context 'Configure' do
# Issue: https://gitlab.com/gitlab-org/gitlab/issues/35156
context 'Configure', :quarantine do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)

View file

@ -15,7 +15,7 @@ describe 'The group page' do
def expect_all_sidebar_links
within('.nav-sidebar') do
expect(page).to have_link('Overview')
expect(page).to have_link('Group overview')
expect(page).to have_link('Details')
expect(page).to have_link('Activity')
expect(page).to have_link('Issues')
@ -44,7 +44,7 @@ describe 'The group page' do
visit group_path(group)
within('.nav-sidebar') do
expect(page).to have_link('Overview')
expect(page).to have_link('Group overview')
expect(page).to have_link('Details')
expect(page).not_to have_link('Activity')
expect(page).not_to have_link('Contribution Analytics')

View file

@ -237,14 +237,28 @@ describe 'Group' do
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
let!(:path) { group_path(group) }
it 'renders projects and groups on the page' do
visit path
visit group_path(group)
wait_for_requests
expect(page).to have_content(nested_group.name)
expect(page).to have_content(project.name)
expect(page).to have_link('Group overview')
end
it 'renders subgroup page with the text "Subgroup overview"' do
visit group_path(nested_group)
wait_for_requests
expect(page).to have_link('Subgroup overview')
end
it 'renders project page with the text "Project overview"' do
visit project_path(project)
wait_for_requests
expect(page).to have_link('Project overview')
end
end

View file

@ -102,5 +102,40 @@ describe 'Projects > Settings > For a forked project', :js do
end
end
end
context 'grafana integration settings form' do
it 'is not present when the feature flag is disabled' do
stub_feature_flags(gfm_grafana_integration: false)
visit project_settings_operations_path(project)
wait_for_requests
expect(page).to have_no_css('.js-grafana-integration')
end
it 'is present when the feature flag is enabled' do
visit project_settings_operations_path(project)
wait_for_requests
within '.js-grafana-integration' do
click_button('Expand')
end
expect(page).to have_content('Grafana URL')
expect(page).to have_content('API Token')
expect(page).to have_button('Save Changes')
fill_in('grafana-url', with: 'http://gitlab-test.grafana.net')
fill_in('grafana-token', with: 'token')
click_button('Save Changes')
wait_for_requests
assert_text('Your changes have been saved')
end
end
end
end

View file

@ -5,4 +5,7 @@
<li>
<a href="#login-pane">Standard</a>
</li>
<li>
<a href="#register-pane">Register</a>
</li>
</ul>

View file

@ -0,0 +1,92 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`grafana integration component default state to match the default snapshot 1`] = `
<section
class="settings no-animate js-grafana-integration"
id="grafana"
>
<div
class="settings-header"
>
<h4
class="js-section-header"
>
Grafana Authentication
</h4>
<glbutton-stub
class="js-settings-toggle"
>
Expand
</glbutton-stub>
<p
class="js-section-sub-header"
>
Embed Grafana charts in GitLab issues.
</p>
</div>
<div
class="settings-content"
>
<form>
<glformgroup-stub
description="Enter the base URL of the Grafana instance."
label="Grafana URL"
label-for="grafana-url"
>
<glforminput-stub
id="grafana-url"
placeholder="https://my-url.grafana.net/my-dashboard"
value="http://test.host"
/>
</glformgroup-stub>
<glformgroup-stub
label="API Token"
label-for="grafana-token"
>
<glforminput-stub
id="grafana-token"
value="someToken"
/>
<p
class="form-text text-muted"
>
Enter the Grafana API Token.
<a
href="https://grafana.com/docs/http_api/auth/#create-api-token"
rel="noopener noreferrer"
target="_blank"
>
More information
<icon-stub
class="vertical-align-middle"
name="external-link"
size="16"
/>
</a>
</p>
</glformgroup-stub>
<glbutton-stub
variant="success"
>
Save Changes
</glbutton-stub>
</form>
</div>
</section>
`;

View file

@ -0,0 +1,124 @@
import { mount, shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import GrafanaIntegration from '~/grafana_integration/components/grafana_integration.vue';
import { createStore } from '~/grafana_integration/store';
import axios from '~/lib/utils/axios_utils';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import { TEST_HOST } from 'helpers/test_constants';
jest.mock('~/lib/utils/url_utility');
jest.mock('~/flash');
describe('grafana integration component', () => {
let wrapper;
let store;
const operationsSettingsEndpoint = `${TEST_HOST}/mock/ops/settings/endpoint`;
const grafanaIntegrationUrl = `${TEST_HOST}`;
const grafanaIntegrationToken = 'someToken';
beforeEach(() => {
store = createStore({
operationsSettingsEndpoint,
grafanaIntegrationUrl,
grafanaIntegrationToken,
});
});
afterEach(() => {
if (wrapper.destroy) {
wrapper.destroy();
createFlash.mockReset();
refreshCurrentPage.mockReset();
}
});
describe('default state', () => {
it('to match the default snapshot', () => {
wrapper = shallowMount(GrafanaIntegration, { store });
expect(wrapper.element).toMatchSnapshot();
});
});
it('renders header text', () => {
wrapper = shallowMount(GrafanaIntegration, { store });
expect(wrapper.find('.js-section-header').text()).toBe('Grafana Authentication');
});
describe('expand/collapse button', () => {
it('renders as an expand button by default', () => {
wrapper = shallowMount(GrafanaIntegration, { store });
const button = wrapper.find(GlButton);
expect(button.text()).toBe('Expand');
});
});
describe('sub-header', () => {
it('renders descriptive text', () => {
wrapper = shallowMount(GrafanaIntegration, { store });
expect(wrapper.find('.js-section-sub-header').text()).toContain(
'Embed Grafana charts in GitLab issues.',
);
});
});
describe('form', () => {
beforeEach(() => {
jest.spyOn(axios, 'patch').mockImplementation();
});
afterEach(() => {
axios.patch.mockReset();
});
describe('submit button', () => {
const findSubmitButton = () => wrapper.find('.settings-content form').find(GlButton);
const endpointRequest = [
operationsSettingsEndpoint,
{
project: {
grafana_integration_attributes: {
grafana_url: grafanaIntegrationUrl,
token: grafanaIntegrationToken,
},
},
},
];
it('submits form on click', () => {
wrapper = mount(GrafanaIntegration, { store });
axios.patch.mockResolvedValue();
findSubmitButton(wrapper).trigger('click');
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled());
});
it('creates flash banner on error', () => {
const message = 'mockErrorMessage';
wrapper = mount(GrafanaIntegration, { store });
axios.patch.mockRejectedValue({ response: { data: { message } } });
findSubmitButton().trigger('click');
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
return wrapper.vm
.$nextTick()
.then(jest.runAllTicks)
.then(() =>
expect(createFlash).toHaveBeenCalledWith(
`There was an error saving your changes. ${message}`,
'alert',
),
);
});
});
});
});

View file

@ -0,0 +1,28 @@
import mutations from '~/grafana_integration/store/mutations';
import createState from '~/grafana_integration/store/state';
describe('grafana integration mutations', () => {
let localState;
beforeEach(() => {
localState = createState();
});
describe('SET_GRAFANA_URL', () => {
it('sets grafanaUrl', () => {
const mockUrl = 'mockUrl';
mutations.SET_GRAFANA_URL(localState, mockUrl);
expect(localState.grafanaUrl).toBe(mockUrl);
});
});
describe('SET_GRAFANA_TOKEN', () => {
it('sets grafanaToken', () => {
const mockToken = 'mockToken';
mutations.SET_GRAFANA_TOKEN(localState, mockToken);
expect(localState.grafanaToken).toBe(mockToken);
});
});
});

View file

@ -1,5 +1,7 @@
import AccessorUtilities from '~/lib/utils/accessor';
import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
import trackData from '~/pages/sessions/new/index';
import Tracking from '~/tracking';
describe('SigninTabsMemoizer', () => {
const fixtureTemplate = 'static/signin_tabs.html';
@ -93,6 +95,50 @@ describe('SigninTabsMemoizer', () => {
});
});
describe('trackData', () => {
beforeEach(() => {
spyOn(Tracking, 'event');
});
describe('with tracking data', () => {
beforeEach(() => {
gon.tracking_data = {
category: 'Growth::Acquisition::Experiment::SignUpFlow',
action: 'start',
label: 'uuid',
property: 'control_group',
};
trackData();
});
it('should track data when the "click" event of the register tab is triggered', () => {
document.querySelector('a[href="#register-pane"]').click();
expect(Tracking.event).toHaveBeenCalledWith(
'Growth::Acquisition::Experiment::SignUpFlow',
'start',
{
label: 'uuid',
property: 'control_group',
},
);
});
});
describe('without tracking data', () => {
beforeEach(() => {
gon.tracking_data = undefined;
trackData();
});
it('should not track data when the "click" event of the register tab is triggered', () => {
document.querySelector('a[href="#register-pane"]').click();
expect(Tracking.event).not.toHaveBeenCalled();
});
});
});
describe('saveData', () => {
beforeEach(() => {
memo = {

View file

@ -171,38 +171,7 @@ describe('test errors', () => {
// see: https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15
if (process.env.BABEL_ENV === 'coverage') {
// exempt these files from the coverage report
const troubleMakers = [
'./blob_edit/blob_bundle.js',
'./boards/components/modal/empty_state.vue',
'./boards/components/modal/footer.js',
'./boards/components/modal/header.js',
'./cycle_analytics/cycle_analytics_bundle.js',
'./cycle_analytics/components/stage_plan_component.js',
'./cycle_analytics/components/stage_staging_component.js',
'./cycle_analytics/components/stage_test_component.js',
'./commit/pipelines/pipelines_bundle.js',
'./diff_notes/diff_notes_bundle.js',
'./diff_notes/components/jump_to_discussion.js',
'./diff_notes/components/resolve_count.js',
'./dispatcher.js',
'./environments/environments_bundle.js',
'./graphs/graphs_bundle.js',
'./issuable/time_tracking/time_tracking_bundle.js',
'./main.js',
'./merge_conflicts/merge_conflicts_bundle.js',
'./merge_conflicts/components/inline_conflict_lines.js',
'./merge_conflicts/components/parallel_conflict_lines.js',
'./monitoring/monitoring_bundle.js',
'./network/network_bundle.js',
'./network/branch_graph.js',
'./profile/profile_bundle.js',
'./protected_branches/protected_branches_bundle.js',
'./snippet/snippet_bundle.js',
'./terminal/terminal_bundle.js',
'./users/users_bundle.js',
'./issue_show/index.js',
'./pages/admin/application_settings/general/index.js',
];
const troubleMakers = ['./pages/admin/application_settings/general/index.js'];
describe('Uncovered files', function() {
const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)];

View file

@ -16,8 +16,16 @@ describe Analytics::CycleAnalytics::ProjectStage do
end
end
it_behaves_like "cycle analytics stage" do
it_behaves_like 'cycle analytics stage' do
let(:parent) { create(:project) }
let(:parent_name) { :project }
end
context 'relative positioning' do
it_behaves_like 'a class that supports relative positioning' do
let(:project) { create(:project) }
let(:factory) { :cycle_analytics_project_stage }
let(:default_params) { { project: project } }
end
end
end

View file

@ -48,7 +48,7 @@ describe 'projects/deployments/_confirm_rollback_modal' do
render
expect(rendered).to have_selector('h4', text: "Rollback environment #{environment.name}?")
expect(rendered).to have_selector('p', text: "This action will run the job defined by staging for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?")
expect(rendered).to have_selector('p', text: "This action will run the job defined by #{environment.name} for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?")
expect(rendered).to have_selector('a.btn-danger', text: 'Rollback')
end