Skip to content
Snippets Groups Projects
Commit 87231973 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 8f764d21
No related branches found
No related tags found
No related merge requests found
Showing
with 654 additions and 337 deletions
Loading
Loading
@@ -1319,7 +1319,7 @@ module API
expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? }
expose :commit_path, expose_nil: false
expose :tag_path, expose_nil: false
expose :evidence_sha, expose_nil: false
expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? }
expose :assets do
expose :assets_count, as: :count do |release, _|
assets_to_exclude = can_download_code? ? [] : [:sources]
Loading
Loading
@@ -1329,7 +1329,7 @@ module API
expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted
end
expose :evidence_file_path, expose_nil: false
expose :evidence_file_path, expose_nil: false, if: ->(_, _) { can_download_code? }
end
expose :_links do
expose :merge_requests_url, expose_nil: false
Loading
Loading
# frozen_string_literal: true
namespace :metrics do
desc "GitLab | Setup common metrics"
task setup_common_metrics: :gitlab_environment do
::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
end
Loading
Loading
@@ -2942,6 +2942,9 @@ msgstr ""
msgid "Callback URL"
msgstr ""
 
msgid "Can deploy manually to"
msgstr ""
msgid "Can override approvers and approvals required per merge request"
msgstr ""
 
Loading
Loading
@@ -2969,6 +2972,9 @@ msgstr ""
msgid "Cancel this job"
msgstr ""
 
msgid "Canceled deploy to"
msgstr ""
msgid "Cancelling Preview"
msgstr ""
 
Loading
Loading
@@ -4334,6 +4340,24 @@ msgstr ""
msgid "Cohorts"
msgstr ""
 
msgid "Cohorts|Inactive users"
msgstr ""
msgid "Cohorts|Month %{month_index}"
msgstr ""
msgid "Cohorts|New users"
msgstr ""
msgid "Cohorts|Registration month"
msgstr ""
msgid "Cohorts|Returning users"
msgstr ""
msgid "Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately."
msgstr ""
msgid "Collapse"
msgstr ""
 
Loading
Loading
@@ -4615,6 +4639,9 @@ msgstr ""
msgid "Container Registry"
msgstr ""
 
msgid "Container Registry tag expiration policies"
msgstr ""
msgid "Container Scanning"
msgstr ""
 
Loading
Loading
@@ -7092,6 +7119,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
 
msgid "Expiration policies for the Container Registry are a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
msgid "Expired"
msgstr ""
 
Loading
Loading
@@ -12141,6 +12171,9 @@ msgstr ""
msgid "Package was removed"
msgstr ""
 
msgid "PackageRegistry|Automatically remove extra images that aren't designed to be kept."
msgstr ""
msgid "PackageRegistry|Copy Maven XML"
msgstr ""
 
Loading
Loading
@@ -12180,6 +12213,9 @@ msgstr ""
msgid "PackageRegistry|Installation"
msgstr ""
 
msgid "PackageRegistry|Keep and protect the images that matter most."
msgstr ""
msgid "PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab."
msgstr ""
 
Loading
Loading
@@ -12192,12 +12228,18 @@ msgstr ""
msgid "PackageRegistry|Package installation"
msgstr ""
 
msgid "PackageRegistry|Read more about the %{helpLinkStart}Container Registry tag retention policies%{helpLinkEnd}"
msgstr ""
msgid "PackageRegistry|Registry Setup"
msgstr ""
 
msgid "PackageRegistry|Remove package"
msgstr ""
 
msgid "PackageRegistry|Tag retention policies are designed to:"
msgstr ""
msgid "PackageRegistry|There are no packages yet"
msgstr ""
 
Loading
Loading
@@ -19533,6 +19575,9 @@ msgstr ""
msgid "View open merge request"
msgstr ""
 
msgid "View previous app"
msgstr ""
msgid "View project labels"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -154,7 +154,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
.and_return(merge_request)
end
 
it 'does not serialize builds in exposed stages', :sidekiq_might_not_need_inline do
it 'does not serialize builds in exposed stages' do
get_show_json
 
json_response.dig('pipeline', 'details', 'stages').tap do |stages|
Loading
Loading
@@ -183,7 +183,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'job is cancelable' do
let(:job) { create(:ci_build, :running, pipeline: pipeline) }
 
it 'cancel_path is present with correct redirect', :sidekiq_might_not_need_inline do
it 'cancel_path is present with correct redirect' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['cancel_path']).to include(CGI.escape(json_response['build_path']))
Loading
Loading
@@ -193,7 +193,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'with web terminal' do
let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) }
 
it 'exposes the terminal path', :sidekiq_might_not_need_inline do
it 'exposes the terminal path' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('job/job_details')
expect(json_response['terminal_path']).to match(%r{/terminal})
Loading
Loading
@@ -268,7 +268,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project.add_maintainer(user) # Need to be a maintianer to view cluster.path
end
 
it 'exposes the deployment information', :sidekiq_might_not_need_inline do
it 'exposes the deployment information' do
get_show_json
 
expect(response).to have_gitlab_http_status(:ok)
Loading
Loading
@@ -292,7 +292,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
sign_in(user)
end
 
it 'user can edit runner', :sidekiq_might_not_need_inline do
it 'user can edit runner' do
get_show_json
 
expect(response).to have_gitlab_http_status(:ok)
Loading
Loading
@@ -312,7 +312,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
sign_in(user)
end
 
it 'user can not edit runner', :sidekiq_might_not_need_inline do
it 'user can not edit runner' do
get_show_json
 
expect(response).to have_gitlab_http_status(:ok)
Loading
Loading
@@ -331,7 +331,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
sign_in(user)
end
 
it 'user can not edit runner', :sidekiq_might_not_need_inline do
it 'user can not edit runner' do
get_show_json
 
expect(response).to have_gitlab_http_status(:ok)
Loading
Loading
@@ -412,7 +412,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'when job has trace' do
let(:job) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
 
it "has_trace is true", :sidekiq_might_not_need_inline do
it "has_trace is true" do
get_show_json
 
expect(response).to match_response_schema('job/job_details')
Loading
Loading
@@ -458,7 +458,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
end
 
context 'user is a maintainer', :sidekiq_might_not_need_inline do
context 'user is a maintainer' do
before do
project.add_maintainer(user)
 
Loading
Loading
@@ -512,7 +512,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
 
def get_show_json
expect { get_show(id: job.id, format: :json) }
.not_to change { Gitlab::GitalyClient.get_request_count }
.to change { Gitlab::GitalyClient.get_request_count }.by(1) # ListCommitsByOid
end
 
def get_show(**extra_params)
Loading
Loading
Loading
Loading
@@ -93,7 +93,7 @@ describe Projects::PipelinesController do
end
 
context 'when performing gitaly calls', :request_store do
it 'limits the Gitaly requests', :sidekiq_might_not_need_inline do
it 'limits the Gitaly requests' do
# Isolate from test preparation (Repository#exists? is also cached in RequestStore)
RequestStore.end!
RequestStore.clear!
Loading
Loading
@@ -101,8 +101,9 @@ describe Projects::PipelinesController do
 
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
 
# ListCommitsByOid, RepositoryExists, HasLocalBranches
expect { get_pipelines_index_json }
.to change { Gitlab::GitalyClient.get_request_count }.by(2)
.to change { Gitlab::GitalyClient.get_request_count }.by(3)
end
end
 
Loading
Loading
Loading
Loading
@@ -184,19 +184,39 @@ describe Projects::ReleasesController do
sign_in(user)
end
 
it 'returns the correct evidence summary as a json' do
subject
context 'when the user is a developer' do
it 'returns the correct evidence summary as a json' do
subject
expect(json_response).to eq(release.evidence.summary)
end
 
expect(json_response).to eq(release.evidence.summary)
context 'when the release was created before evidence existed' do
before do
release.evidence.destroy
end
it 'returns an empty json' do
subject
expect(json_response).to eq({})
end
end
end
 
context 'when the release was created before evidence existed' do
it 'returns an empty json' do
release.evidence.destroy
context 'when the user is a guest for the project' do
before do
project.add_guest(user)
end
 
subject
context 'when the project is private' do
let(:project) { private_project }
it_behaves_like 'not found'
end
 
expect(json_response).to eq({})
context 'when the project is public' do
it_behaves_like 'successful request'
end
end
end
Loading
Loading
Loading
Loading
@@ -43,6 +43,7 @@ describe 'Database schema' do
geo_nodes: %w[oauth_application_id],
geo_repository_deleted_events: %w[project_id],
geo_upload_deleted_events: %w[upload_id model_id],
gitlab_subscription_histories: %w[gitlab_subscription_id hosted_plan_id namespace_id],
import_failures: %w[project_id],
identities: %w[user_id],
issues: %w[last_edited_by_id state_id],
Loading
Loading
Loading
Loading
@@ -96,7 +96,7 @@ describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request)
wait_for_requests
 
expect(page).to have_content("Failed to deploy to #{environment.name}")
expect(page).to have_content("Canceled deploy to #{environment.name}")
expect(page).not_to have_css('.js-deploy-time')
end
end
Loading
Loading
Loading
Loading
@@ -45,6 +45,7 @@ describe('Issuable component', () => {
...props,
},
sync: false,
attachToDocument: true,
});
};
 
Loading
Loading
Loading
Loading
@@ -49,6 +49,7 @@ describe('Issuables list component', () => {
},
localVue,
sync: false,
attachToDocument: true,
});
};
 
Loading
Loading
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Registry List renders 1`] = `
<div>
<p>
Tag retention policies are designed to:
</p>
<ul>
<li>
Keep and protect the images that matter most.
</li>
<li>
Automatically remove extra images that aren't designed to be kept.
</li>
</ul>
<p>
Read more about the
<a
href="foo"
target="_blank"
>
Container Registry tag retention policies
</a>
</p>
</div>
`;
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import component from '~/registry/settings/components/registry_settings_app.vue';
import { createStore } from '~/registry/settings/stores/';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Registry List', () => {
let wrapper;
let store;
const helpPagePath = 'foo';
const findHelpLink = () => wrapper.find({ ref: 'help-link' }).find('a');
const mountComponent = (options = {}) =>
shallowMount(component, {
sync: false,
store,
...options,
});
beforeEach(() => {
store = createStore();
store.dispatch('setInitialState', { helpPagePath });
wrapper = mountComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('renders an help link dependant on the helphPagePath', () => {
expect(findHelpLink().attributes('href')).toBe(helpPagePath);
});
});
import * as actions from '~/registry/settings/stores/actions';
import * as types from '~/registry/settings/stores/mutation_types';
import testAction from 'helpers/vuex_action_helper';
jest.mock('~/flash.js');
describe('Actions Registry Store', () => {
describe('setInitialState', () => {
it('should set the initial state', done => {
testAction(
actions.setInitialState,
'foo',
{},
[{ type: types.SET_INITIAL_STATE, payload: 'foo' }],
[],
done,
);
});
});
});
import mutations from '~/registry/settings/stores/mutations';
import * as types from '~/registry/settings/stores/mutation_types';
import createState from '~/registry/settings/stores/state';
describe('Mutations Registry Store', () => {
let mockState;
beforeEach(() => {
mockState = createState();
});
describe('SET_INITIAL_STATE', () => {
it('should set the initial state', () => {
const payload = { helpPagePath: 'foo', registrySettingsEndpoint: 'bar' };
const expectedState = { ...mockState, ...payload };
mutations[types.SET_INITIAL_STATE](mockState, payload);
expect(mockState.endpoint).toEqual(expectedState.endpoint);
});
});
});
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
const deploymentMockData = {
id: 15,
name: 'review/diplo',
url: '/root/review-apps/environments/15',
stop_url: '/root/review-apps/environments/15/stop',
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
external_url: 'http://gitlab.com.',
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
details: {},
status: SUCCESS,
changes: [
{
path: 'index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
},
{
path: 'imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
},
{
path: 'about/',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
export default deploymentMockData;
import { mount } from '@vue/test-utils';
import DeploymentComponent from '~/vue_merge_request_widget/components/deployment/deployment.vue';
import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import DeploymentStopButton from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
import {
CREATED,
RUNNING,
SUCCESS,
FAILED,
CANCELED,
} from '~/vue_merge_request_widget/components/deployment/constants';
import deploymentMockData from './deployment_mock_data';
const deployDetail = {
playable_build: {
retry_path: '/root/test-deployments/-/jobs/1131/retry',
play_path: '/root/test-deployments/-/jobs/1131/play',
},
isManual: true,
};
describe('Deployment component', () => {
let wrapper;
const factory = (options = {}) => {
// This destroys any wrappers created before a nested call to factory reassigns it
if (wrapper && wrapper.destroy) {
wrapper.destroy();
}
wrapper = mount(DeploymentComponent, {
...options,
});
};
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
showMetrics: false,
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('always renders DeploymentInfo', () => {
expect(wrapper.find(DeploymentInfo).exists()).toBe(true);
});
describe('status message and buttons', () => {
const noActions = [];
const noDetails = { isManual: false };
const deployGroup = [DeploymentViewButton, DeploymentStopButton];
describe.each`
status | previous | deploymentDetails | text | actionButtons
${CREATED} | ${true} | ${deployDetail} | ${'Can deploy manually to'} | ${deployGroup}
${CREATED} | ${true} | ${noDetails} | ${'Will deploy to'} | ${deployGroup}
${CREATED} | ${false} | ${deployDetail} | ${'Can deploy manually to'} | ${noActions}
${CREATED} | ${false} | ${noDetails} | ${'Will deploy to'} | ${noActions}
${RUNNING} | ${true} | ${deployDetail} | ${'Deploying to'} | ${deployGroup}
${RUNNING} | ${true} | ${noDetails} | ${'Deploying to'} | ${deployGroup}
${RUNNING} | ${false} | ${deployDetail} | ${'Deploying to'} | ${noActions}
${RUNNING} | ${false} | ${noDetails} | ${'Deploying to'} | ${noActions}
${SUCCESS} | ${true} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
${SUCCESS} | ${true} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
${SUCCESS} | ${false} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
${SUCCESS} | ${false} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
${FAILED} | ${true} | ${deployDetail} | ${'Failed to deploy to'} | ${deployGroup}
${FAILED} | ${true} | ${noDetails} | ${'Failed to deploy to'} | ${deployGroup}
${FAILED} | ${false} | ${deployDetail} | ${'Failed to deploy to'} | ${noActions}
${FAILED} | ${false} | ${noDetails} | ${'Failed to deploy to'} | ${noActions}
${CANCELED} | ${true} | ${deployDetail} | ${'Canceled deploy to'} | ${deployGroup}
${CANCELED} | ${true} | ${noDetails} | ${'Canceled deploy to'} | ${deployGroup}
${CANCELED} | ${false} | ${deployDetail} | ${'Canceled deploy to'} | ${noActions}
${CANCELED} | ${false} | ${noDetails} | ${'Canceled deploy to'} | ${noActions}
`(
'$status + previous: $previous + manual: $deploymentDetails.isManual',
({ status, previous, deploymentDetails, text, actionButtons }) => {
beforeEach(() => {
const previousOrSuccess = Boolean(previous || status === SUCCESS);
const updatedDeploymentData = {
status,
deployed_at: previous ? deploymentMockData.deployed_at : null,
deployed_at_formatted: previous ? deploymentMockData.deployed_at_formatted : null,
external_url: previousOrSuccess ? deploymentMockData.external_url : null,
external_url_formatted: previousOrSuccess
? deploymentMockData.external_url_formatted
: null,
stop_url: previousOrSuccess ? deploymentMockData.stop_url : null,
details: deploymentDetails,
};
factory({
propsData: {
showMetrics: false,
deployment: {
...deploymentMockData,
...updatedDeploymentData,
},
},
});
});
it(`renders the text: ${text}`, () => {
expect(wrapper.find(DeploymentInfo).text()).toContain(text);
});
if (actionButtons.length > 0) {
describe('renders the expected button group', () => {
actionButtons.forEach(button => {
it(`renders ${button.name}`, () => {
expect(wrapper.find(button).exists()).toBe(true);
});
});
});
}
if (actionButtons.length === 0) {
describe('does not render the button group', () => {
[DeploymentViewButton, DeploymentStopButton].forEach(button => {
it(`does not render ${button.name}`, () => {
expect(wrapper.find(button).exists()).toBe(false);
});
});
});
}
if (actionButtons.includes(DeploymentViewButton)) {
it('renders the View button with expected text', () => {
if (status === SUCCESS) {
expect(wrapper.find(DeploymentViewButton).text()).toContain('View app');
} else {
expect(wrapper.find(DeploymentViewButton).text()).toContain('View previous app');
}
});
}
},
);
});
describe('hasExternalUrls', () => {
describe('when deployment has both external_url_formatted and external_url', () => {
it('should return true', () => {
expect(wrapper.vm.hasExternalUrls).toEqual(true);
});
it('should render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(true);
});
});
describe('when deployment has no external_url_formatted', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, external_url_formatted: null },
showMetrics: false,
},
});
});
it('should return false', () => {
expect(wrapper.vm.hasExternalUrls).toEqual(false);
});
it('should not render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
});
});
describe('when deployment has no external_url', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, external_url: null },
showMetrics: false,
},
});
});
it('should return false', () => {
expect(wrapper.vm.hasExternalUrls).toEqual(false);
});
it('should not render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
});
});
});
});
import { mount, createLocalVue } from '@vue/test-utils';
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import ReviewAppLink from '~/vue_merge_request_widget/components/review_app_link.vue';
import deploymentMockData from './deployment_mock_data';
describe('Deployment View App button', () => {
let wrapper;
const factory = (options = {}) => {
const localVue = createLocalVue();
wrapper = mount(localVue.extend(DeploymentViewButton), {
localVue,
...options,
});
};
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
isCurrent: true,
},
});
});
afterEach(() => {
wrapper.destroy();
});
describe('text', () => {
describe('when app is current', () => {
it('shows View app', () => {
expect(wrapper.find(ReviewAppLink).text()).toContain('View app');
});
});
describe('when app is not current', () => {
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
isCurrent: false,
},
});
});
it('shows View Previous app', () => {
expect(wrapper.find(ReviewAppLink).text()).toContain('View previous app');
});
});
});
describe('without changes', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, changes: null },
isCurrent: false,
},
});
});
it('renders the link to the review app without dropdown', () => {
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
});
});
describe('with a single change', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, changes: [deploymentMockData.changes[0]] },
isCurrent: false,
},
});
});
it('renders the link to the review app without dropdown', () => {
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
});
it('renders the link to the review app linked to to the first change', () => {
const expectedUrl = deploymentMockData.changes[0].external_url;
const deployUrl = wrapper.find('.js-deploy-url');
expect(deployUrl.attributes().href).not.toBeNull();
expect(deployUrl.attributes().href).toEqual(expectedUrl);
});
});
describe('with multiple changes', () => {
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
isCurrent: false,
},
});
});
it('renders the link to the review app with dropdown', () => {
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(true);
});
it('renders all the links to the review apps', () => {
const allUrls = wrapper.findAll('.js-deploy-url-menu-item').wrappers;
const expectedUrls = deploymentMockData.changes.map(change => change.external_url);
expectedUrls.forEach((expectedUrl, idx) => {
const deployUrl = allUrls[idx];
expect(deployUrl.attributes().href).not.toBeNull();
expect(deployUrl.attributes().href).toEqual(expectedUrl);
});
});
});
});
import Vue from 'vue';
import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import { getTimeago } from '~/lib/utils/datetime_utility';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Deployment component', () => {
const Component = Vue.extend(deploymentComponent);
let deploymentMockData;
beforeEach(() => {
deploymentMockData = {
id: 15,
name: 'review/diplo',
url: '/root/review-apps/environments/15',
stop_url: '/root/review-apps/environments/15/stop',
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
external_url: 'http://gitlab.com.',
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes: [
{
path: 'index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
},
{
path: 'imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
},
{
path: 'about/',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
});
let vm;
afterEach(() => {
vm.$destroy();
});
describe('', () => {
beforeEach(() => {
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
});
describe('deployTimeago', () => {
it('return formatted date', () => {
const readable = getTimeago().format(deploymentMockData.deployed_at);
expect(vm.deployTimeago).toEqual(readable);
});
});
describe('hasExternalUrls', () => {
it('should return true', () => {
expect(vm.hasExternalUrls).toEqual(true);
});
it('should return false when deployment has no external_url_formatted', () => {
vm.deployment.external_url_formatted = null;
expect(vm.hasExternalUrls).toEqual(false);
});
it('should return false when deployment has no external_url', () => {
vm.deployment.external_url = null;
expect(vm.hasExternalUrls).toEqual(false);
});
});
describe('hasDeploymentTime', () => {
it('should return true', () => {
expect(vm.hasDeploymentTime).toEqual(true);
});
it('should return false when deployment has no deployed_at', () => {
vm.deployment.deployed_at = null;
expect(vm.hasDeploymentTime).toEqual(false);
});
it('should return false when deployment has no deployed_at_formatted', () => {
vm.deployment.deployed_at_formatted = null;
expect(vm.hasDeploymentTime).toEqual(false);
});
});
describe('hasDeploymentMeta', () => {
it('should return true', () => {
expect(vm.hasDeploymentMeta).toEqual(true);
});
it('should return false when deployment has no url', () => {
vm.deployment.url = null;
expect(vm.hasDeploymentMeta).toEqual(false);
});
it('should return false when deployment has no name', () => {
vm.deployment.name = null;
expect(vm.hasDeploymentMeta).toEqual(false);
});
});
describe('stopEnvironment', () => {
const url = '/foo/bar';
const returnPromise = () =>
new Promise(resolve => {
resolve({
data: {
redirect_url: url,
},
});
});
const mockStopEnvironment = () => {
vm.stopEnvironment(deploymentMockData);
return vm;
};
it('should show a confirm dialog and call service.stopEnvironment when confirmed', done => {
spyOn(window, 'confirm').and.returnValue(true);
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
const visitUrl = spyOnDependency(deploymentComponent, 'visitUrl').and.returnValue(true);
vm = mockStopEnvironment();
expect(window.confirm).toHaveBeenCalled();
expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
setTimeout(() => {
expect(visitUrl).toHaveBeenCalledWith(url);
done();
}, 333);
});
it('should show a confirm dialog but should not work if the dialog is rejected', () => {
spyOn(window, 'confirm').and.returnValue(false);
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
vm = mockStopEnvironment();
expect(window.confirm).toHaveBeenCalled();
expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
});
});
it('renders deployment name', () => {
expect(vm.$el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(
deploymentMockData.url,
);
expect(vm.$el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
});
it('renders external URL', () => {
expect(vm.$el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(
deploymentMockData.external_url,
);
expect(vm.$el.querySelector('.js-deploy-url').innerText).toContain('View app');
});
it('renders stop button', () => {
expect(vm.$el.querySelector('.btn')).not.toBeNull();
});
it('renders deployment time', () => {
expect(vm.$el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
});
it('renders metrics component', () => {
expect(vm.$el.querySelector('.js-mr-memory-usage')).not.toBeNull();
});
});
describe('with showMetrics enabled', () => {
beforeEach(() => {
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
});
it('shows metrics', () => {
expect(vm.$el).toContainElement('.js-mr-memory-usage');
});
});
describe('with showMetrics disabled', () => {
beforeEach(() => {
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: false });
});
it('hides metrics', () => {
expect(vm.$el).not.toContainElement('.js-mr-memory-usage');
});
});
describe('without changes', () => {
beforeEach(() => {
delete deploymentMockData.changes;
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
});
it('renders the link to the review app without dropdown', () => {
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
});
});
describe('with a single change', () => {
beforeEach(() => {
deploymentMockData.changes = deploymentMockData.changes.slice(0, 1);
vm = mountComponent(Component, {
deployment: { ...deploymentMockData },
showMetrics: true,
});
});
it('renders the link to the review app without dropdown', () => {
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
});
it('renders the link to the review app linked to to the first change', () => {
const expectedUrl = deploymentMockData.changes[0].external_url;
const deployUrl = vm.$el.querySelector('.js-deploy-url');
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
expect(deployUrl).not.toBeNull();
expect(deployUrl.href).toEqual(expectedUrl);
});
});
describe('deployment status', () => {
describe('running', () => {
beforeEach(() => {
vm = mountComponent(Component, {
deployment: Object.assign({}, deploymentMockData, { status: 'running' }),
showMetrics: true,
});
});
it('renders information about running deployment', () => {
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deploying to');
});
it('renders disabled stop button', () => {
expect(vm.$el.querySelector('.js-stop-env').getAttribute('disabled')).toBe('disabled');
});
});
describe('success', () => {
beforeEach(() => {
vm = mountComponent(Component, {
deployment: Object.assign({}, deploymentMockData, { status: 'success' }),
showMetrics: true,
});
});
it('renders information about finished deployment', () => {
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deployed to');
});
});
describe('failed', () => {
beforeEach(() => {
vm = mountComponent(Component, {
deployment: Object.assign({}, deploymentMockData, { status: 'failed' }),
showMetrics: true,
});
});
it('renders information about finished deployment', () => {
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
'Failed to deploy to',
);
});
});
describe('created', () => {
beforeEach(() => {
vm = mountComponent(Component, {
deployment: Object.assign({}, deploymentMockData, { status: 'created' }),
showMetrics: true,
});
});
it('renders information about created deployment', () => {
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Will deploy to');
});
});
describe('canceled', () => {
beforeEach(() => {
vm = mountComponent(Component, {
deployment: Object.assign({}, deploymentMockData, { status: 'canceled' }),
showMetrics: true,
});
});
it('renders information about canceled deployment', () => {
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
'Failed to deploy to',
);
});
});
});
});
import Vue from 'vue';
import deploymentStopComponent from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Deployment component', () => {
const Component = Vue.extend(deploymentStopComponent);
let deploymentMockData;
beforeEach(() => {
deploymentMockData = {
id: 15,
name: 'review/diplo',
url: '/root/review-apps/environments/15',
stop_url: '/root/review-apps/environments/15/stop',
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
external_url: 'http://gitlab.com.',
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
deployment_manual_actions: [],
status: SUCCESS,
changes: [
{
path: 'index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
},
{
path: 'imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
},
{
path: 'about/',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
});
let vm;
afterEach(() => {
vm.$destroy();
});
describe('', () => {
beforeEach(() => {
vm = mountComponent(Component, {
stopUrl: deploymentMockData.stop_url,
isDeployInProgress: false,
});
});
describe('stopEnvironment', () => {
const url = '/foo/bar';
const returnPromise = () =>
new Promise(resolve => {
resolve({
data: {
redirect_url: url,
},
});
});
const mockStopEnvironment = () => {
vm.stopEnvironment(deploymentMockData);
return vm;
};
it('should show a confirm dialog and call service.stopEnvironment when confirmed', done => {
spyOn(window, 'confirm').and.returnValue(true);
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
const visitUrl = spyOnDependency(deploymentStopComponent, 'visitUrl').and.returnValue(true);
vm = mockStopEnvironment();
expect(window.confirm).toHaveBeenCalled();
expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
setTimeout(() => {
expect(visitUrl).toHaveBeenCalledWith(url);
done();
}, 333);
});
it('should show a confirm dialog but should not work if the dialog is rejected', () => {
spyOn(window, 'confirm').and.returnValue(false);
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
vm = mockStopEnvironment();
expect(window.confirm).toHaveBeenCalled();
expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
});
});
});
});
import Vue from 'vue';
import MemoryUsage from '~/vue_merge_request_widget/components/memory_usage.vue';
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
 
const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment