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>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create">
New Environment
</a>
</p> </p>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create">
New Environment
</a>
</div> </div>
<div <div
...@@ -227,4 +245,4 @@ $(() => { ...@@ -227,4 +245,4 @@ $(() => {
</div> </div>
`, `,
}); });
}); })();
...@@ -44,18 +44,19 @@ ...@@ -44,18 +44,19 @@
<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"
<span class="action-play-icon-container"> data-method="post"
<!-- svg goes here --> rel="nofollow"
</span> class="js-manual-action-link">
<span v-html="action.name"></span> <span class="action-play-icon-container">
</span>
<span v-html="action.name"></span>
</a> </a>
</li> </li>
</ul> </ul>
......
...@@ -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,20 +26,27 @@ feature 'Environments', feature: true do ...@@ -26,20 +26,27 @@ 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)
end end
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,233 +10,138 @@ feature 'Environments', feature: true, js: true do ...@@ -10,233 +10,138 @@ 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) { }
before do before do
visit_environments(project) visit_environments(project)
end end
context 'shows two tabs' do
scenario 'shows "Available" and "Stopped" tab with links' do
expect(page).to have_link('Available')
expect(page).to have_link('Stopped')
end
end
context 'without environments' do
scenario 'does show no environments' do
expect(page).to have_content('You don\'t have any environments right now.')
end
scenario 'does show 0 as counter for environments in both tabs' do describe 'page tabs' do
expect(page.find('.js-available-environments-count').text).to eq('0') scenario 'shows "Available" and "Stopped" tab with links' do
expect(page.find('.js-stopped-environments-count').text).to eq('0') expect(page).to have_link('Available')
end expect(page).to have_link('Stopped')
end end
end
context 'with environments' do context 'without environments' do
given(:environment) { create(:environment, project: project) } scenario 'does show no environments' do
expect(page).to have_content('You don\'t have any environments right now.')
scenario 'does show environment name' do
expect(page).to have_link(environment.name)
end
scenario 'does show number of available and stopped environments' do
expect(page.find('.js-available-environments-count').text).to eq('1')
expect(page.find('.js-stopped-environments-count').text).to eq('0')
end
context 'without deployments' do
scenario 'does show no deployments' do
expect(page).to have_content('No deployments yet')
end
end
context 'with deployments' do
given(:project) { create(:project) }
given(:deployment) do
create(:deployment, environment: environment,
sha: project.commit.id)
end
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
end
scenario 'does show deployment internal id' do
expect(page).to have_content(deployment.iid)
end
context 'with build and manual actions' do
given(:pipeline) { create(:ci_pipeline, project: project) }
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') }
scenario 'does show a play button' do
find('.dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize)
end
scenario 'does allow to play manual action' do
expect(manual).to be_skipped
find('.dropdown-play-icon-container').click
play_action = find('span', text: 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(manual.reload).to be_pending
end
scenario 'does show build name and id' do
expect(page).to have_link("#{build.name} ##{build.id}")
end
scenario 'does not show stop button' do
expect(page).not_to have_selector('.stop-env-link')
end
scenario 'does not show external link button' do
expect(page).not_to have_css('external-url')
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_selector('.stop-env-link')
end
scenario 'starts build when stop button clicked' do
find('.stop-env-link').click
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_selector('.stop-env-link')
end
end
end
end
end
end end
scenario 'does have a New environment button' do scenario 'does show 0 as counter for environments in both tabs' do
expect(page).to have_link('New environment') expect(page.find('.js-available-environments-count').text).to eq('0')
expect(page.find('.js-stopped-environments-count').text).to eq('0')
end end
end end
describe 'when showing the environment' do describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) } given(:environment) { create(:environment, project: project) }
given!(:deployment) { }
given!(:manual) { }
before do scenario 'does show environment name' do
visit_environment(environment) expect(page).to have_link(environment.name)
end
scenario 'does show number of available and stopped environments' do
expect(page.find('.js-available-environments-count').text).to eq('1')
expect(page.find('.js-stopped-environments-count').text).to eq('0')
end end
context 'without deployments' do context 'without deployments' do
scenario 'does show no deployments' do scenario 'does show no deployments' do
expect(page).to have_content('You don\'t have any deployments right now.') expect(page).to have_content('No deployments yet')
end end
end end
context 'with deployments' do context 'with deployments' do
given(:project) { create(:project) }
given(:deployment) do given(:deployment) do
create(:deployment, environment: environment, deployable: nil) create(:deployment, environment: environment,
sha: project.commit.id)
end 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)
end end
scenario 'does not show a re-deploy button for deployment without build' do scenario 'does show deployment internal id' do
expect(page).not_to have_link('Re-deploy') expect(page).to have_content(deployment.iid)
end end
context 'with build' 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) }
scenario 'does show build name' do given(:manual) do
expect(page).to have_link("#{build.name} (##{build.id})") create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production')
end end
scenario 'does show re-deploy button' do given(:deployment) do
expect(page).to have_link('Re-deploy') create(:deployment, environment: environment,
deployable: build,
sha: project.commit.id)
end end
scenario 'does not show stop button' do scenario 'does show a play button' do
expect(page).not_to have_link('Stop') find('.dropdown-play-icon-container').click
expect(page).to have_content(manual.name.humanize)
end end
context 'with manual action' do scenario 'does allow to play manual action', js: true do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } expect(manual).to be_skipped
scenario 'does show a play button' do find('.dropdown-play-icon-container').click
expect(page).to have_link(manual.name.humanize) expect(page).to have_content(manual.name.humanize)
end
scenario 'does allow to play manual action' do expect { click_link(manual.name.humanize) }
expect(manual).to be_skipped .not_to change { Ci::Pipeline.count }
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 expect(manual.reload).to be_pending
given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } end
given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show an external link button' do scenario 'does show build name and id' do
expect(page).to have_link(nil, href: environment.external_url) expect(page).to have_link("#{build.name} ##{build.id}")
end end
scenario 'does not show stop button' do
expect(page).not_to have_selector('.stop-env-link')
end
scenario 'does not show external link button' do
expect(page).not_to have_css('external-url')
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
end
context 'with stop action' do context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
scenario 'does show stop button' do scenario 'does show stop button' do
expect(page).to have_link('Stop') expect(page).to have_selector('.stop-env-link')
end end
scenario 'does allow to stop environment' do scenario 'starts build when stop button clicked' do
click_link('Stop') find('.stop-env-link').click
expect(page).to have_content('close_app') expect(page).to have_content('close_app')
end end
context 'for reporter' do context 'for reporter' do
let(:role) { :reporter } let(:role) { :reporter }
scenario 'does not show stop button' do scenario 'does not show stop button' do
expect(page).not_to have_link('Stop') expect(page).not_to have_selector('.stop-env-link')
end
end end
end end
end end
...@@ -244,6 +149,10 @@ feature 'Environments', feature: true, js: true do ...@@ -244,6 +149,10 @@ feature 'Environments', feature: true, js: true do
end end
end end
scenario 'does have a New environment button' do
expect(page).to have_link('New environment')
end
describe 'when creating a new environment' do describe 'when creating a new environment' do
before do before do
visit_environments(project) visit_environments(project)
...@@ -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