Commit bd8d4d2e authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge branch '22539-display-folders' of gitlab.com:gitlab-org/gitlab-ce into 22539-display-folders

parents 8d001844 25f99d84
...@@ -6,15 +6,36 @@ ...@@ -6,15 +6,36 @@
/* globals Vue, EnvironmentsService */ /* globals Vue, EnvironmentsService */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
$(() => { (() => { // eslint-disable-line
window.gl = window.gl || {}; window.gl = window.gl || {};
const filterState = state => environment => environment.state === state && environment; /**
* Given the visibility prop provided by the url query parameter and which
* changes according to the active tab we need to filter which environments
* should be visible.
*
* The environments array is a recursive tree structure and we need to filter
* both root level environments and children environments.
*
* In order to acomplish that, both `filterState` and `filterEnvironmnetsByState`
* functions work together.
* The first one works as the filter that verifies if the given environment matches
* the given state.
* The second guarantees both root level and children elements are filtered as well.
*/
// recursiveMap :: (Function, Array) -> Array const filterState = state => environment => environment.state === state && environment;
const recursiveMap = (fn, arr) => arr.map((item) => { /**
* Given the filter function and the array of environments will return only
* the environments that match the state provided to the filter function.
*
* @param {Function} fn
* @param {Array} array
* @return {Array}
*/
const filterEnvironmnetsByState = (fn, arr) => arr.map((item) => {
if (item.children) { if (item.children) {
const filteredChildren = recursiveMap(fn, item.children).filter(Boolean); const filteredChildren = filterEnvironmnetsByState(fn, item.children).filter(Boolean);
if (filteredChildren.length) { if (filteredChildren.length) {
item.children = filteredChildren; item.children = filteredChildren;
return item; return item;
...@@ -37,26 +58,27 @@ $(() => { ...@@ -37,26 +58,27 @@ $(() => {
}, },
data() { data() {
const environmentsListApp = document.querySelector('#environments-list-view'); const environmentsData = document.querySelector('#environments-list-view').dataset;
return { return {
state: this.store.state, state: this.store.state,
endpoint: environmentsListApp.dataset.environmentsDataEndpoint,
canCreateDeployment: environmentsListApp.dataset.canCreateDeployment,
canReadEnvironment: environmentsListApp.dataset.canReadEnvironment,
canCreateEnvironment: environmentsListApp.dataset.canCreateEnvironment,
projectEnvironmentsPath: environmentsListApp.dataset.projectEnvironmentsPath,
projectStoppedEnvironmentsPath: environmentsListApp.dataset.projectStoppedEnvironmentsPath,
newEnvironmentPath: environmentsListApp.dataset.newEnvironmentPath,
helpPagePath: environmentsListApp.dataset.helpPagePath,
visibility: 'available', visibility: 'available',
isLoading: false, isLoading: false,
cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment,
canCreateEnvironment: environmentsData.canCreateEnvironment,
projectEnvironmentsPath: environmentsData.projectEnvironmentsPath,
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
}; };
}, },
computed: { computed: {
filteredEnvironments() { filteredEnvironments() {
return recursiveMap(filterState(this.visibility), this.state.environments); return filterEnvironmnetsByState(filterState(this.visibility), this.state.environments);
}, },
scope() { scope() {
...@@ -81,7 +103,7 @@ $(() => { ...@@ -81,7 +103,7 @@ $(() => {
* Toggles loading property. * Toggles loading property.
*/ */
created() { created() {
window.gl.environmentsService = new EnvironmentsService(this.endpoint); gl.environmentsService = new EnvironmentsService(this.endpoint);
const scope = this.$options.getQueryParameter('scope'); const scope = this.$options.getQueryParameter('scope');
if (scope) { if (scope) {
...@@ -90,7 +112,7 @@ $(() => { ...@@ -90,7 +112,7 @@ $(() => {
this.isLoading = true; this.isLoading = true;
return window.gl.environmentsService.all() return gl.environmentsService.all()
.then(resp => resp.json()) .then(resp => resp.json())
.then((json) => { .then((json) => {
this.store.storeEnvironments(json); this.store.storeEnvironments(json);
...@@ -119,10 +141,7 @@ $(() => { ...@@ -119,10 +141,7 @@ $(() => {
* @returns {Boolean} * @returns {Boolean}
*/ */
convertPermissionToBoolean(string) { convertPermissionToBoolean(string) {
if (string === 'true') { return string === 'true';
return true;
}
return false;
}, },
methods: { methods: {
...@@ -132,10 +151,10 @@ $(() => { ...@@ -132,10 +151,10 @@ $(() => {
}, },
template: ` template: `
<div class="container-fluid container-limited"> <div :class="cssContainerClass">
<div class="top-area"> <div class="top-area">
<ul v-if="!isLoading" class="nav-links"> <ul v-if="!isLoading" class="nav-links">
<li v-bind:class="{ 'active': scope === undefined}"> <li v-bind:class="{ 'active': scope === undefined }">
<a :href="projectEnvironmentsPath"> <a :href="projectEnvironmentsPath">
Available Available
<span <span
...@@ -143,7 +162,7 @@ $(() => { ...@@ -143,7 +162,7 @@ $(() => {
v-html="state.availableCounter"></span> v-html="state.availableCounter"></span>
</a> </a>
</li> </li>
<li v-bind:class="{ 'active' : scope === 'stopped'}"> <li v-bind:class="{ 'active' : scope === 'stopped' }">
<a :href="projectStoppedEnvironmentsPath"> <a :href="projectStoppedEnvironmentsPath">
Stopped Stopped
<span <span
...@@ -172,19 +191,18 @@ $(() => { ...@@ -172,19 +191,18 @@ $(() => {
</h2> </h2>
<p class="blank-state-text"> <p class="blank-state-text">
Environments are places where code gets deployed, such as staging or production. Environments are places where code gets deployed, such as staging or production.
<br /> <br />
<a :href="helpPagePath"> <a :href="helpPagePath">
Read more about environments Read more about environments
</a> </a>
</p>
<a <a
v-if="canCreateEnvironmentParsed" v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath" :href="newEnvironmentPath"
class="btn btn-create"> class="btn btn-create">
New Environment New Environment
</a> </a>
</p>
</div> </div>
<div <div
...@@ -227,4 +245,4 @@ $(() => { ...@@ -227,4 +245,4 @@ $(() => {
</div> </div>
`, `,
}); });
}); })();
...@@ -44,16 +44,17 @@ ...@@ -44,16 +44,17 @@
<div class="dropdown"> <div class="dropdown">
<a class="dropdown-new btn btn-default" data-toggle="dropdown"> <a class="dropdown-new btn btn-default" data-toggle="dropdown">
<span class="dropdown-play-icon-container"> <span class="dropdown-play-icon-container">
<!-- svg goes here -->
</span> </span>
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down"></i>
</a> </a>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li v-for="action in actions">
<a :href="action.play_url" data-method="post" data-rel="nofollow" class="js-manual-action-link"> <a :href="action.play_path"
data-method="post"
rel="nofollow"
class="js-manual-action-link">
<span class="action-play-icon-container"> <span class="action-play-icon-container">
<!-- svg goes here -->
</span> </span>
<span v-html="action.name"></span> <span v-html="action.name"></span>
</a> </a>
......
...@@ -76,8 +76,7 @@ ...@@ -76,8 +76,7 @@
* @returns {Boolean|Undefined} * @returns {Boolean|Undefined}
*/ */
isFolder() { isFolder() {
return this.model.children && return this.model.children && this.model.children.length > 0;
this.model.children.length > 0;
}, },
/** /**
...@@ -97,8 +96,7 @@ ...@@ -97,8 +96,7 @@
* @returns {Number|Undefined} The number of environments for the current folder. * @returns {Number|Undefined} The number of environments for the current folder.
*/ */
childrenCounter() { childrenCounter() {
return this.model.children && return this.model.children && this.model.children.length;
this.model.children.length;
}, },
/** /**
...@@ -109,7 +107,8 @@ ...@@ -109,7 +107,8 @@
* @returns {Boolean} * @returns {Boolean}
*/ */
hasLastDeploymentKey() { hasLastDeploymentKey() {
if (this.model.last_deployment && this.model.last_deployment !== {}) { if (this.model.last_deployment &&
!this.$options.isObjectEmpty(this.model.last_deployment)) {
return true; return true;
} }
return false; return false;
...@@ -168,7 +167,7 @@ ...@@ -168,7 +167,7 @@
return this.model.last_deployment.manual_actions.map((action) => { return this.model.last_deployment.manual_actions.map((action) => {
const parsedAction = { const parsedAction = {
name: gl.text.humanize(action.name), name: gl.text.humanize(action.name),
play_url: action.play_url, play_path: action.play_path,
}; };
return parsedAction; return parsedAction;
}); });
...@@ -209,8 +208,7 @@ ...@@ -209,8 +208,7 @@
* @returns {Object|Undefined} * @returns {Object|Undefined}
*/ */
commitRef() { commitRef() {
if (this.model.last_deployment && if (this.model.last_deployment && this.model.last_deployment.ref) {
this.model.last_deployment.ref) {
return this.model.last_deployment.ref; return this.model.last_deployment.ref;
} }
return undefined; return undefined;
...@@ -224,8 +222,8 @@ ...@@ -224,8 +222,8 @@
commitUrl() { commitUrl() {
if (this.model.last_deployment && if (this.model.last_deployment &&
this.model.last_deployment.commit && this.model.last_deployment.commit &&
this.model.last_deployment.commit.commit_url) { this.model.last_deployment.commit.commit_path) {
return this.model.last_deployment.commit.commit_url; return this.model.last_deployment.commit.commit_path;
} }
return undefined; return undefined;
}, },
...@@ -274,15 +272,15 @@ ...@@ -274,15 +272,15 @@
}, },
/** /**
* Verifies if the `retry_url` key is present and returns its value. * Verifies if the `retry_path` key is present and returns its value.
* *
* @returns {String|Undefined} * @returns {String|Undefined}
*/ */
retryUrl() { retryUrl() {
if (this.model.last_deployment && if (this.model.last_deployment &&
this.model.last_deployment.deployable && this.model.last_deployment.deployable &&
this.model.last_deployment.deployable.retry_url) { this.model.last_deployment.deployable.retry_path) {
return this.model.last_deployment.deployable.retry_url; return this.model.last_deployment.deployable.retry_path;
} }
return undefined; return undefined;
}, },
...@@ -328,11 +326,8 @@ ...@@ -328,11 +326,8 @@
* @returns {Boolean} * @returns {Boolean}
*/ */
deploymentHasUser() { deploymentHasUser() {
if (this.model.last_deployment && return !this.$options.isObjectEmpty(this.model.last_deployment) &&
this.model.last_deployment.user) { !this.$options.isObjectEmpty(this.model.last_deployment.user);
return true;
}
return false;
}, },
/** /**
...@@ -342,11 +337,53 @@ ...@@ -342,11 +337,53 @@
* @returns {Object} * @returns {Object}
*/ */
deploymentUser() { deploymentUser() {
if (this.model.last_deployment && this.model.last_deployment.user) { if (!this.$options.isObjectEmpty(this.model.last_deployment) &&
!this.$options.isObjectEmpty(this.model.last_deployment.user)) {
return this.model.last_deployment.user; return this.model.last_deployment.user;
} }
return {}; return {};
}, },
/**
* Verifies if the build name column should be rendered by verifing
* if all the information needed is present
* and if the environment is not a folder.
*
* @returns {Boolean}
*/
shouldRenderBuildName() {
return !this.isFolder &&
!this.$options.isObjectEmpty(this.model.last_deployment) &&
!this.$options.isObjectEmpty(this.model.last_deployment.deployable);
},
/**
* Verifies if deplyment internal ID should be rendered by verifing
* if all the information needed is present
* and if the environment is not a folder.
*
* @returns {Boolean}
*/
shouldRenderDeploymentID() {
return !this.isFolder &&
!this.$options.isObjectEmpty(this.model.last_deployment) &&
this.model.last_deployment.iid !== undefined;
},
},
/**
* Helper to verify if certain given object are empty.
* Should be replaced by lodash _.isEmpty - https://lodash.com/docs/4.17.2#isEmpty
* @param {Object} object
* @returns {Bollean}
*/
isObjectEmpty(object) {
for (const key in object) { // eslint-disable-line
if (hasOwnProperty.call(object, key)) {
return false;
}
}
return true;
}, },
template: ` template: `
...@@ -355,7 +392,7 @@ ...@@ -355,7 +392,7 @@
<a <a
v-if="!isFolder" v-if="!isFolder"
class="environment-name" class="environment-name"
:href="model.environment_url" :href="model.environment_path"
v-html="model.name"> v-html="model.name">
</a> </a>
<span v-else v-on:click="toggleRow(model)" class="folder-name"> <span v-else v-on:click="toggleRow(model)" class="folder-name">
...@@ -372,7 +409,7 @@ ...@@ -372,7 +409,7 @@
<td class="deployment-column"> <td class="deployment-column">
<span <span
v-if="!isFolder && model.last_deployment && model.last_deployment.iid" v-if="shouldRenderDeploymentID"
v-html="deploymentInternalId"> v-html="deploymentInternalId">
</span> </span>
...@@ -388,9 +425,9 @@ ...@@ -388,9 +425,9 @@
</td> </td>
<td> <td>
<a v-if="!isFolder && model.last_deployment && model.last_deployment.deployable" <a v-if="shouldRenderBuildName"
class="build-link" class="build-link"
:href="model.last_deployment.deployable.build_url" :href="model.last_deployment.deployable.build_path"
v-html="buildName"> v-html="buildName">
</a> </a>
</td> </td>
...@@ -421,25 +458,29 @@ ...@@ -421,25 +458,29 @@
<td class="hidden-xs"> <td class="hidden-xs">
<div v-if="!isFolder"> <div v-if="!isFolder">
<div v-if="hasManualActions && canCreateDeployment" class="inline js-manual-actions-container"> <div v-if="hasManualActions && canCreateDeployment"
class="inline js-manual-actions-container">
<actions-component <actions-component
:actions="manualActions"> :actions="manualActions">
</actions-component> </actions-component>
</div> </div>
<div v-if="model.external_url && canReadEnvironment" class="inline js-external-url-container"> <div v-if="model.external_url && canReadEnvironment"
class="inline js-external-url-container">
<external-url-component <external-url-component
:external_url="model.external_url"> :external_url="model.external_url">
</external_url-component> </external_url-component>
</div> </div>
<div v-if="isStoppable && canCreateDeployment" class="inline js-stop-component-container"> <div v-if="isStoppable && canCreateDeployment"
class="inline js-stop-component-container">
<stop-component <stop-component
:stop_url="model.environment_url"> :stop_url="model.environment_path">
</stop-component> </stop-component>
</div> </div>
<div v-if="canRetry && canCreateDeployment" class="inline js-rollback-component-container"> <div v-if="canRetry && canCreateDeployment"
class="inline js-rollback-component-container">
<rollback-component <rollback-component
:is_last_deployment="isLastDeployment" :is_last_deployment="isLastDeployment"
:retry_url="retryUrl"> :retry_url="retryUrl">
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
class="btn stop-env-link" class="btn stop-env-link"
:href="stopUrl" :href="stopUrl"
data-method="post" data-method="post"
data-rel="nofollow"> rel="nofollow">
<i class="fa fa-stop stop-env-icon"></i> <i class="fa fa-stop stop-env-icon"></i>
</a> </a>
`, `,
......
...@@ -89,17 +89,17 @@ ...@@ -89,17 +89,17 @@
toggleFolder(envType) { toggleFolder(envType) {
const environments = this.state.environments; const environments = this.state.environments;
const environmnetsCopy = environments.map((env) => { const environmentsCopy = environments.map((env) => {
if (env['vue-isChildren'] === true && env.name === envType) { if (env['vue-isChildren'] && env.name === envType) {
env.isOpen = !env.isOpen; env.isOpen = !env.isOpen;
} }
return env; return env;
}); });
this.state.environments = environmnetsCopy; this.state.environments = environmentsCopy;
return environmnetsCopy; return environmentsCopy;
}, },
/** /**
...@@ -125,15 +125,7 @@ ...@@ -125,15 +125,7 @@
const nameA = a.name.toUpperCase(); const nameA = a.name.toUpperCase();
const nameB = b.name.toUpperCase(); const nameB = b.name.toUpperCase();
if (nameA < nameB) { return nameA < nameB ? -1 : nameA > nameB ? 1 : 0; // eslint-disable-line
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
}, },
}; };
})(); })();
...@@ -143,7 +143,6 @@ ...@@ -143,7 +143,6 @@
</a> </a>
<div class="icon-container commit-icon commit-icon-container"> <div class="icon-container commit-icon commit-icon-container">
<!-- svg goes here -->
</div> </div>
<a class="commit-id monospace" <a class="commit-id monospace"
...@@ -153,7 +152,6 @@ ...@@ -153,7 +152,6 @@
<p class="commit-title"> <p class="commit-title">
<span v-if="title"> <span v-if="title">
<!-- commit author info-->
<a v-if="hasAuthor" <a v-if="hasAuthor"
class="avatar-image-container" class="avatar-image-container"
:href="author.web_url"> :href="author.web_url">
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
} }
.environments { .environments {
table-layout: fixed;
.deployment-column { .deployment-column {
.avatar { .avatar {
float: none; float: none;
......
...@@ -4,21 +4,21 @@ class BuildEntity < Grape::Entity ...@@ -4,21 +4,21 @@ class BuildEntity < Grape::Entity
expose :id expose :id
expose :name expose :name
expose :build_url do |build| expose :build_path do |build|
url_to(:namespace_project_build, build) path_to(:namespace_project_build, build)
end end
expose :retry_url do |build| expose :retry_path do |build|
url_to(:retry_namespace_project_build, build) path_to(:retry_namespace_project_build, build)
end end
expose :play_url, if: ->(build, _) { build.manual? } do |build| expose :play_path, if: ->(build, _) { build.manual? } do |build|
url_to(:play_namespace_project_build, build) path_to(:play_namespace_project_build, build)
end end
private private
def url_to(route, build) def path_to(route, build)
send("#{route}_url", build.project.namespace, build.project, build) send("#{route}_path", build.project.namespace, build.project, build)
end end
end end
...@@ -3,8 +3,8 @@ class CommitEntity < API::Entities::RepoCommit ...@@ -3,8 +3,8 @@ class CommitEntity < API::Entities::RepoCommit
expose :author, using: UserEntity expose :author, using: UserEntity
expose :commit_url do |commit| expose :commit_path do |commit|
namespace_project_tree_url( namespace_project_tree_path(
request.project.namespace, request.project.namespace,
request.project, request.project,
id: commit.id) id: commit.id)
......
...@@ -10,8 +10,8 @@ class DeploymentEntity < Grape::Entity ...@@ -10,8 +10,8 @@ class DeploymentEntity < Grape::Entity
deployment.ref deployment.ref
end end
expose :ref_url do |deployment| expose :ref_path do |deployment|
namespace_project_tree_url( namespace_project_tree_path(
deployment.project.namespace, deployment.project.namespace,
deployment.project, deployment.project,
id: deployment.ref) id: deployment.ref)
......
...@@ -9,8 +9,8 @@ class EnvironmentEntity < Grape::Entity ...@@ -9,8 +9,8 @@ class EnvironmentEntity < Grape::Entity
expose :last_deployment, using: DeploymentEntity expose :last_deployment, using: DeploymentEntity
expose :stoppable? expose :stoppable?
expose :environment_url do |environment| expose :environment_path do |environment|
namespace_project_environment_url( namespace_project_environment_path(
environment.project.namespace, environment.project.namespace,
environment.project, environment.project,
environment) environment)
......
...@@ -16,4 +16,5 @@ ...@@ -16,4 +16,5 @@
"project-environments-path" => project_environments_path(@project), "project-environments-path" => project_environments_path(@project),
"project-stopped-environments-path" => project_environments_path(@project, scope: :stopped), "project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
"new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project), "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project),
"help-page-path" => help_page_path("ci/environments")}, class: container_class } "help-page-path" => help_page_path("ci/environments"),
"css-class" => container_class}}
require 'spec_helper' require 'spec_helper'
feature 'Environments', feature: true do feature 'Environment', :feature do
given(:project) { create(:empty_project) } given(:project) { create(:empty_project) }
given(:user) { create(:user) } given(:user) { create(:user) }
given(:role) { :developer } given(:role) { :developer }
...@@ -10,13 +10,13 @@ feature 'Environments', feature: true do ...@@ -10,13 +10,13 @@ feature 'Environments', feature: true do
project.team << [user, role] project.team << [user, role]
end end
describe 'when showing the environment' do feature 'environment details page' do
given(:environment) { create(:environment, project: project) } given!(:environment) { create(:environment, project: project) }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { } given!(:manual) { }
before do before do
visit namespace_project_environment_path(project.namespace, project, environment) visit_environment(environment)
end end
context 'without deployments' do context 'without deployments' do
...@@ -26,7 +26,10 @@ feature 'Environments', feature: true do ...@@ -26,7 +26,10 @@ feature 'Environments', feature: true do
end end
context 'with deployments' do context 'with deployments' do
given(:deployment) { create(:deployment, environment: environment) } context 'when there is no related deployable' do
given(:deployment) do
create(:deployment, environment: environment, deployable: nil)
end
scenario 'does show deployment SHA' do scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha) expect(page).to have_link(deployment.short_sha)
...@@ -35,11 +38,15 @@ feature 'Environments', feature: true do ...@@ -35,11 +38,15 @@ feature 'Environments', feature: true do
scenario 'does not show a re-deploy button for deployment without build' do scenario 'does not show a re-deploy button for deployment without build' do
expect(page).not_to have_link('Re-deploy') expect(page).not_to have_link('Re-deploy')
end end
end
context 'with build' do context 'with related deployable present' do
given(:pipeline) { create(:ci_pipeline, project: project) } given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) } given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
given(:deployment) do
create(:deployment, environment: environment, deployable: build)
end
scenario 'does show build name' do scenario 'does show build name' do
expect(page).to have_link("#{build.name} (##{build.id})") expect(page).to have_link("#{build.name} (##{build.id})")
...@@ -57,7 +64,6 @@ feature 'Environments', feature: true do ...@@ -57,7 +64,6 @@ feature 'Environments', feature: true do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
scenario 'does show a play button' do scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize) expect(page).to have_link(manual.name.humanize)
end end
...@@ -104,4 +110,52 @@ feature 'Environments', feature: true do ...@@ -104,4 +110,52 @@ feature 'Environments', feature: true do
end end
end end
end end
feature 'auto-close environment when branch is deleted' do
given(:project) { create(:project) }
given!(:environment) do
create(:environment, :with_review_app, project: project,
ref: 'feature')
end
scenario 'user visits environment page' do
visit_environment(environment)
expect(page).to have_link('Stop')
end
scenario 'user deletes the branch with running environment' do
visit namespace_project_branches_path(project.namespace, project)
remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click }
end
visit_environment(environment)
expect(page).to have_no_link('Stop')
end
##
# This is a workaround for problem described in #24543
#
def remove_branch_with_hooks(project, user, branch)
params = {
oldrev: project.commit(branch).id,
newrev: Gitlab::Git::BLANK_SHA,
ref: "refs/heads/#{branch}"
}
yield
GitPushService.new(project, user, params).execute
end
end
def visit_environment(environment)
visit namespace_project_environment_path(environment.project.namespace,
environment.project,
environment)
end
end end
require 'spec_helper' require 'spec_helper'
feature 'Environments', feature: true, js: true do feature 'Environments page', :feature, :js do
given(:project) { create(:empty_project) } given(:project) { create(:empty_project) }
given(:user) { create(:user) } given(:user) { create(:user) }
given(:role) { :developer } given(:role) { :developer }
...@@ -10,7 +10,6 @@ feature 'Environments', feature: true, js: true do ...@@ -10,7 +10,6 @@ feature 'Environments', feature: true, js: true do
login_as(user) login_as(user)
end end
describe 'when showing environments' do
given!(:environment) { } given!(:environment) { }
given!(:deployment) { } given!(:deployment) { }
given!(:manual) { } given!(:manual) { }
...@@ -19,7 +18,7 @@ feature 'Environments', feature: true, js: true do ...@@ -19,7 +18,7 @@ feature 'Environments', feature: true, js: true do
visit_environments(project) visit_environments(project)
end end
context 'shows two tabs' do describe 'page tabs' do
scenario 'shows "Available" and "Stopped" tab with links' do scenario 'shows "Available" and "Stopped" tab with links' do
expect(page).to have_link('Available') expect(page).to have_link('Available')
expect(page).to have_link('Stopped') expect(page).to have_link('Stopped')
...@@ -37,7 +36,7 @@ feature 'Environments', feature: true, js: true do ...@@ -37,7 +36,7 @@ feature 'Environments', feature: true, js: true do
end end
end end
context 'with environments' do describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) } given(:environment) { create(:environment, project: project) }
scenario 'does show environment name' do scenario 'does show environment name' do
...@@ -74,24 +73,31 @@ feature 'Environments', feature: true, js: true do ...@@ -74,24 +73,31 @@ feature 'Environments', feature: true, js: true do
context 'with build and manual actions' do context 'with build and manual actions' do
given(:pipeline) { create(:ci_pipeline, project: project) } given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) } given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } given(:manual) do
create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production')
end
given(:deployment) do
create(:deployment, environment: environment,
deployable: build,
sha: project.commit.id)
end
scenario 'does show a play button' do scenario 'does show a play button' do
find('.dropdown-play-icon-container').click find('.dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize) expect(page).to have_content(manual.name.humanize)
end end
scenario 'does allow to play manual action' do scenario 'does allow to play manual action', js: true do
expect(manual).to be_skipped expect(manual).to be_skipped
find('.dropdown-play-icon-container').click find('.dropdown-play-icon-container').click
play_action = find('span', text: manual.name.humanize)
expect(page).to have_content(manual.name.humanize) expect(page).to have_content(manual.name.humanize)
expect { play_action.click }.not_to change { Ci::Pipeline.count }
# TODO, fix me! expect { click_link(manual.name.humanize) }
.not_to change { Ci::Pipeline.count }
expect(manual.reload).to be_pending expect(manual.reload).to be_pending
end end
...@@ -146,103 +152,6 @@ feature 'Environments', feature: true, js: true do ...@@ -146,103 +152,6 @@ feature 'Environments', feature: true, js: true do
scenario 'does have a New environment button' do scenario 'does have a New environment button' do
expect(page).to have_link('New environment') expect(page).to have_link('New environment')
end end
end
describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) }
given!(:deployment) { }
given!(:manual) { }
before do
visit_environment(environment)
end
context 'without deployments' do
scenario 'does show no deployments' do
expect(page).to have_content('You don\'t have any deployments right now.')
end
end
context 'with deployments' do
given(:deployment) do
create(:deployment, environment: environment, deployable: nil)
end
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
end
scenario 'does not show a re-deploy button for deployment without build' do
expect(page).not_to have_link('Re-deploy')
end
context 'with build' do
given(:pipeline) { create(:ci_pipeline, project: project) }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show build name' do
expect(page).to have_link("#{build.name} (##{build.id})")
end
scenario 'does show re-deploy button' do
expect(page).to have_link('Re-deploy')
end
scenario 'does not show stop button' do
expect(page).not_to have_link('Stop')
end
context 'with manual action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
scenario 'does show a play button' do
expect(page).to have_link(manual.name.humanize)
end
scenario 'does allow to play manual action' do
expect(manual).to be_skipped
expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
expect(page).to have_content(manual.name)
expect(manual.reload).to be_pending
end
context 'with external_url' do
given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') }
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show an external link button' do
expect(page).to have_link(nil, href: environment.external_url)
end
end
context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
scenario 'does show stop button' do
expect(page).to have_link('Stop')
end
scenario 'does allow to stop environment' do
click_link('Stop')
expect(page).to have_content('close_app')
end
context 'for reporter' do
let(:role) { :reporter }
scenario 'does not show stop button' do
expect(page).not_to have_link('Stop')
end
end
end
end
end
end
end
describe 'when creating a new environment' do describe 'when creating a new environment' do
before do before do
...@@ -286,55 +195,7 @@ feature 'Environments', feature: true, js: true do ...@@ -286,55 +195,7 @@ feature 'Environments', feature: true, js: true do
end end
end end
feature 'auto-close environment when branch deleted' do
given(:project) { create(:project) }
given!(:environment) do
create(:environment, :with_review_app, project: project,
ref: 'feature')
end
scenario 'user visits environment page' do
visit_environment(environment)
expect(page).to have_link('Stop')
end
scenario 'user deletes the branch with running environment' do
visit namespace_project_branches_path(project.namespace, project)
remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click }
end
visit_environment(environment)
expect(page).to have_no_link('Stop')
end
##
# This is a workaround for problem described in #24543
#
def remove_branch_with_hooks(project, user, branch)
params = {
oldrev: project.commit(branch).id,
newrev: Gitlab::Git::BLANK_SHA,
ref: "refs/heads/#{branch}"
}
yield
GitPushService.new(project, user, params).execute
end
end
def visit_environments(project) def visit_environments(project)
visit namespace_project_environments_path(project.namespace, project) visit namespace_project_environments_path(project.namespace, project)
end end
def visit_environment(environment)
visit namespace_project_environment_path(environment.project.namespace,
environment.project,
environment)
end
end end
...@@ -40,18 +40,18 @@ const environmentsList = [ ...@@ -40,18 +40,18 @@ const environmentsList = [
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root', web_url: 'http://localhost:3000/root',
}, },
commit_url: 'http://localhost:3000/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd', commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
}, },
deployable: { deployable: {
id: 1278, id: 1278,
name: 'build', name: 'build',
build_url: 'http://localhost:3000/root/ci-folders/builds/1278', build_path: '/root/ci-folders/builds/1278',
retry_url: 'http://localhost:3000/root/ci-folders/builds/1278/retry', retry_path: '/root/ci-folders/builds/1278/retry',
}, },
manual_actions: [], manual_actions: [],
}, },
'stoppable?': true, 'stoppable?': true,
environment_url: 'http://localhost:3000/root/ci-folders/environments/31', environment_path: '/root/ci-folders/environments/31',
created_at: '2016-11-07T11:11:16.525Z', created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-07T11:11:16.525Z', updated_at: '2016-11-07T11:11:16.525Z',
}, },
...@@ -95,18 +95,18 @@ const environmentsList = [ ...@@ -95,18 +95,18 @@ const environmentsList = [
avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root', web_url: 'http://localhost:3000/root',
}, },
commit_url: 'http://localhost:3000/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd', commit_path: '/root/ci-folders/tree/500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
}, },
deployable: { deployable: {
id: 1278, id: 1278,
name: 'build', name: 'build',
build_url: 'http://localhost:3000/root/ci-folders/builds/1278', build_path: '/root/ci-folders/builds/1278',
retry_url: 'http://localhost:3000/root/ci-folders/builds/1278/retry', retry_path: '/root/ci-folders/builds/1278/retry',
}, },
manual_actions: [], manual_actions: [],
}, },
'stoppable?': false, 'stoppable?': false,
environment_url: 'http://localhost:3000/root/ci-folders/environments/31', environment_path: '/root/ci-folders/environments/31',
created_at: '2016-11-07T11:11:16.525Z', created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-07T11:11:16.525Z', updated_at: '2016-11-07T11:11:16.525Z',
}, },
...@@ -117,7 +117,7 @@ const environmentsList = [ ...@@ -117,7 +117,7 @@ const environmentsList = [
environment_type: 'review', environment_type: 'review',
last_deployment: null, last_deployment: null,
'stoppable?': true, 'stoppable?': true,
environment_url: 'http://localhost:3000/root/ci-folders/environments/31', environment_path: '/root/ci-folders/environments/31',
created_at: '2016-11-07T11:11:16.525Z', created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-07T11:11:16.525Z', updated_at: '2016-11-07T11:11:16.525Z',
}, },
...@@ -128,9 +128,8 @@ const environmentsList = [ ...@@ -128,9 +128,8 @@ const environmentsList = [
environment_type: 'review', environment_type: 'review',
last_deployment: null, last_deployment: null,
'stoppable?': true, 'stoppable?': true,
environment_url: 'http://localhost:3000/root/ci-folders/environments/31', environment_path: '/root/ci-folders/environments/31',
created_at: '2016-11-07T11:11:16.525Z', created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-07T11:11:16.525Z', updated_at: '2016-11-07T11:11:16.525Z',
}, },
]; ];
...@@ -10,9 +10,9 @@ describe BuildEntity do ...@@ -10,9 +10,9 @@ describe BuildEntity do
context 'when build is a regular job' do context 'when build is a regular job' do
let(:build) { create(:ci_build) } let(:build) { create(:ci_build) }
it 'contains url to build page and retry action' do it 'contains paths to build page and retry action' do
expect(subject).to include(:build_url, :retry_url) expect(subject).to include(:build_path, :retry_path)
expect(subject).not_to include(:play_url) expect(subject).not_to include(:play_path)
end end
it 'does not contain sensitive information' do it 'does not contain sensitive information' do
...@@ -24,8 +24,8 @@ describe BuildEntity do ...@@ -24,8 +24,8 @@ describe BuildEntity do
context 'when build is a manual action' do context 'when build is a manual action' do
let(:build) { create(:ci_build, :manual) } let(:build) { create(:ci_build, :manual) }
it 'contains url to play action' do it 'contains path to play action' do
expect(subject).to include(:play_url) expect(subject).to include(:play_path)
end end
end end
end end
...@@ -31,8 +31,8 @@ describe CommitEntity do ...@@ -31,8 +31,8 @@ describe CommitEntity do
end end
end end
it 'contains commit URL' do it 'contains path to commit' do
expect(subject).to include(:commit_url) expect(subject).to include(:commit_path)
end end
it 'needs to receive project in the request' do it 'needs to receive project in the request' do
......
...@@ -15,6 +15,6 @@ describe DeploymentEntity do ...@@ -15,6 +15,6 @@ describe DeploymentEntity do
it 'exposes nested information about branch' do it 'exposes nested information about branch' do
expect(subject[:ref][:name]).to eq 'master' expect(subject[:ref][:name]).to eq 'master'
expect(subject[:ref][:ref_url]).not_to be_empty expect(subject[:ref][:ref_path]).not_to be_empty
end end
end end
...@@ -13,6 +13,6 @@ describe EnvironmentEntity do ...@@ -13,6 +13,6 @@ describe EnvironmentEntity do
end end
it 'exposes core elements of environment' do it 'exposes core elements of environment' do
expect(subject).to include(:id, :name, :state, :environment_url) expect(subject).to include(:id, :name, :state, :environment_path)
end end
end end
...@@ -33,7 +33,7 @@ describe EnvironmentSerializer do ...@@ -33,7 +33,7 @@ describe EnvironmentSerializer do
it 'contains important elements of environment' do it 'contains important elements of environment' do
expect(json) expect(json)
.to include(:name, :external_url, :environment_url, :last_deployment) .to include(:name, :external_url, :environment_path, :last_deployment)
end end
it 'contains relevant information about last deployment' do it 'contains relevant information about last deployment' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment