Commit 44b07ee2 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch '3328-one-request-deploy-boards' into 'master'

Resolve "Improve visibility of deploy boards"

Closes #3328

See merge request gitlab-org/gitlab-ee!2984
parents 45d76e75 f5340667
......@@ -8,7 +8,9 @@
* - Button Actions.
* [Mockup](https://gitlab.com/gitlab-org/gitlab-ce/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png)
*/
import _ from 'underscore';
import deployBoardSvg from 'empty_states/icons/_deploy_board.svg';
import { n__ } from '../../locale';
import instanceComponent from './deploy_board_instance_component.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
......@@ -26,40 +28,27 @@
type: Boolean,
required: true,
},
hasError: {
isEmpty: {
type: Boolean,
required: true,
},
},
data() {
return {
deployBoardSvg,
};
},
computed: {
canRenderDeployBoard() {
return !this.isLoading && !this.hasError && this.deployBoardData.valid;
return !this.isLoading && !this.isEmpty && !_.isEmpty(this.deployBoardData);
},
canRenderEmptyState() {
return !this.isLoading && !this.hasError && !this.deployBoardData.valid;
},
canRenderErrorState() {
return !this.isLoading && this.hasError;
return !this.isLoading && this.isEmpty;
},
instanceTitle() {
let title;
if (this.deployBoardData.instances.length === 1) {
title = 'Instance';
} else {
title = 'Instances';
}
return title;
return n__('Instance', 'Instances', this.deployBoardData.instances.length);
},
projectName() {
return '<projectname>';
},
deployBoardSvg() {
return deployBoardSvg;
},
},
};
</script>
......@@ -128,11 +117,5 @@
</span>
</section>
</div>
<div
v-if="canRenderErrorState"
class="deploy-board-error-message">
We can't fetch the data right now. Please try again later.
</div>
</div>
</script>
......@@ -127,16 +127,10 @@ export default {
/**
* Toggles the visibility of the deploy boards of the clicked environment.
*
* @param {Object} model
* @return {Object}
* @param {Object} model
*/
toggleDeployBoard(model) {
this.store.toggleDeployBoard(model.id);
if (!model.isDeployboardVisible) {
this.fetchDeployBoard(model, true);
}
},
toggleFolder(folder) {
......@@ -203,11 +197,6 @@ export default {
if (openFolders.length) {
openFolders.forEach(folder => this.fetchChildEnvironments(folder));
}
const openDeployBoards = this.store.getOpenDeployBoards();
if (openDeployBoards.length) {
openDeployBoards.forEach(env => this.fetchDeployBoard(env));
}
},
errorCallback() {
......@@ -215,23 +204,6 @@ export default {
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
},
fetchDeployBoard(environment, showLoader = false) {
this.store.updateEnvironmentProp(environment, 'isLoadingDeployBoard', showLoader);
this.service.getDeployBoard(environment.rollout_status_path)
.then(resp => resp.json())
.then((data) => {
this.store.storeDeployBoard(environment.id, data);
this.store.updateEnvironmentProp(environment, 'isLoadingDeployBoard', false);
})
.catch(() => {
this.store.updateEnvironmentProp(environment, 'isLoadingDeployBoard', false);
this.store.updateEnvironmentProp(environment, 'hasErrorDeployBoard', true);
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the deploy board.');
});
},
},
};
</script>
......
......@@ -74,7 +74,7 @@ export default {
<deploy-board
:deploy-board-data="model.deployBoardData"
:is-loading="model.isLoadingDeployBoard"
:has-error="model.hasErrorDeployBoard"
:is-empty="model.isEmptyDeployBoard"
/>
</div>
</div>
......
......@@ -30,7 +30,7 @@ export default class EnvironmentsStore {
* If the `size` is bigger than 1, it means it should be rendered as a folder.
* In those cases we add `isFolder` key in order to render it properly.
*
* Top level environments - when the size is 1 - with `rollout_status_path`
* Top level environments - when the size is 1 - with `rollout_status`
* can render a deploy board. We add `isDeployBoardVisible` and `deployBoardData`
* keys to those environments.
* The first key will let's us know if we should or not render the deploy board.
......@@ -65,13 +65,15 @@ export default class EnvironmentsStore {
filtered = Object.assign(filtered, env);
}
if (filtered.size === 1 && filtered.rollout_status_path) {
if (filtered.size === 1 && filtered.rollout_status) {
filtered = Object.assign({}, filtered, {
hasDeployBoard: true,
isDeployBoardVisible: oldEnvironmentState.isDeployBoardVisible || false,
deployBoardData: oldEnvironmentState.deployBoardData || {},
isLoadingDeployBoard: oldEnvironmentState.isLoadingDeployBoard || false,
hasErrorDeployBoard: oldEnvironmentState.hasErrorDeployBoard || false,
isDeployBoardVisible: oldEnvironmentState.isDeployBoardVisible === false ?
oldEnvironmentState.isDeployBoardVisible :
true,
deployBoardData: filtered.rollout_status.status === 'found' ? filtered.rollout_status : {},
isLoadingDeployBoard: filtered.rollout_status.status === 'loading',
isEmptyDeployBoard: filtered.rollout_status.status === 'not_found',
});
}
return filtered;
......
class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_environment!
before_action :authorize_read_deploy_board!, only: :status
before_action :authorize_create_environment!, only: [:new, :create]
before_action :authorize_create_deployment!, only: [:stop]
before_action :authorize_update_environment!, only: [:edit, :update]
before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics, :status]
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
def index
@environments = project.environments
......@@ -132,25 +132,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
# The rollout status of an enviroment
def status
unless @environment.deployment_service_ready?
render text: 'Not found', status: 404
return
end
rollout_status = @environment.rollout_status
Gitlab::PollingInterval.set_header(response, interval: 3000) unless rollout_status.try(:complete?)
if rollout_status.nil?
render body: nil, status: 204 # no result yet
else
serializer = RolloutStatusSerializer.new(project: @project, current_user: @current_user)
render json: serializer.represent(rollout_status)
end
end
def additional_metrics
respond_to do |format|
format.json do
......@@ -167,6 +148,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController
Gitlab::Workhorse.verify_api_request!(request.headers)
end
def expire_etag_cache
return if request.format.json?
# this forces to reload json content
Gitlab::EtagCaching::Store.new.tap do |store|
store.touch(project_environments_path(project, format: :json))
end
end
def environment_params
params.require(:environment).permit(:name, :external_url)
end
......
......@@ -59,6 +59,9 @@ module ReactiveCaching
raise NotImplementedError
end
def reactive_cache_updated(*args)
end
def with_reactive_cache(*args, &blk)
within_reactive_cache_lifetime(*args) do
data = Rails.cache.read(full_reactive_cache_key(*args))
......@@ -77,8 +80,11 @@ module ReactiveCaching
locking_reactive_cache(*args) do
within_reactive_cache_lifetime(*args) do
enqueuing_update(*args) do
value = calculate_reactive_cache(*args)
Rails.cache.write(full_reactive_cache_key(*args), value)
key = full_reactive_cache_key(*args)
new_value = calculate_reactive_cache(*args)
old_value = Rails.cache.read(key)
Rails.cache.write(key, new_value)
reactive_cache_updated(*args) if new_value != old_value
end
end
end
......
......@@ -17,12 +17,14 @@ class MockDeploymentService < DeploymentService
end
def rollout_status(environment)
OpenStruct.new(
instances: rollout_status_instances,
completion: 80,
valid?: true,
complete?: true
)
case environment.name
when 'staging'
Gitlab::Kubernetes::RolloutStatus.new([], status: :not_found)
when 'test'
Gitlab::Kubernetes::RolloutStatus.new([], status: :loading)
else
Gitlab::Kubernetes::RolloutStatus.new(rollout_status_deployments)
end
end
private
......@@ -31,4 +33,8 @@ class MockDeploymentService < DeploymentService
data = File.read(Rails.root.join('spec', 'fixtures', 'rollout_status_instances.json'))
JSON.parse(data)
end
def rollout_status_deployments
[OpenStruct.new(instances: rollout_status_instances)]
end
end
......@@ -9,7 +9,9 @@ class EnvironmentEntity < Grape::Entity
expose :last_deployment, using: DeploymentEntity
expose :stop_action?
expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
expose :rollout_status, if: -> (*) { can_read_deploy_board? }, using: RolloutStatusEntity
expose :metrics_path, if: -> (*) { environment.has_metrics? } do |environment|
metrics_project_environment_path(environment.project, environment)
end
......@@ -21,19 +23,26 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
end
expose :terminal_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment|
expose :terminal_path, if: ->(*) { environment.deployment_service_ready? } do |environment|
can?(request.current_user, :admin_environment, environment.project) &&
terminal_project_environment_path(environment.project, environment)
end
expose :rollout_status_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment|
can?(request.current_user, :read_deploy_board, environment.project) &&
status_project_environment_path(environment.project, environment, format: :json)
end
expose :folder_path do |environment|
folder_project_environments_path(environment.project, environment.folder_name)
end
expose :created_at, :updated_at
private
alias_method :environment, :object
def current_user
request.current_user
end
def can_read_deploy_board?
can?(current_user, :read_deploy_board, environment.project)
end
end
class RolloutStatusEntity < Grape::Entity
include RequestAwareEntity
expose :instances
expose :completion
expose :valid?, as: :valid
expose :status, as: :status
expose :is_completed do |rollout_status|
rollout_status.complete?
end
expose :instances, if: -> (rollout_status, _) { rollout_status.found? }
expose :completion, if: -> (rollout_status, _) { rollout_status.found? }
expose :complete?, as: :is_completed, if: -> (rollout_status, _) { rollout_status.found? }
end
class RolloutStatusSerializer < BaseSerializer
entity RolloutStatusEntity
end
---
title: Improves visibility of deploy boards
merge_request:
author:
type: changed
......@@ -224,7 +224,6 @@ constraints(ProjectUrlConstrainer.new) do
get :terminal
get :metrics
get :additional_metrics
get :status, constraints: { format: :json }
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
end
......
module EE
module KubernetesService
def rollout_status(environment)
with_reactive_cache do |data|
result = with_reactive_cache do |data|
specs = filter_by_label(data[:deployments], app: environment.slug)
::Gitlab::Kubernetes::RolloutStatus.from_specs(*specs)
end
result || ::Gitlab::Kubernetes::RolloutStatus.loading
end
def calculate_reactive_cache
......@@ -15,6 +16,15 @@ module EE
result
end
def reactive_cache_updated
super
::Gitlab::EtagCaching::Store.new.tap do |store|
store.touch(
::Gitlab::Routing.url_helpers.project_environments_path(project, format: :json))
end
end
def read_deployments
kubeclient = build_kubeclient!(api_path: 'apis/extensions', api_version: 'v1beta1')
......
......@@ -6,26 +6,38 @@ module Gitlab
# other resources, unified by an `app=` label. The rollout status sums the
# Kubernetes deployments together.
class RolloutStatus
attr_reader :deployments, :instances, :completion
attr_reader :deployments, :instances, :completion, :status
def complete?
completion == 100
end
def valid?
@valid
def loading?
@status == :loading
end
def not_found?
@status == :not_found
end
def found?
@status == :found
end
def self.from_specs(*specs)
return new([], valid: false) if specs.empty?
return new([], status: :not_found) if specs.empty?
deployments = specs.map { |spec| ::Gitlab::Kubernetes::Deployment.new(spec) }
deployments.sort_by!(&:order)
new(deployments)
end
def initialize(deployments, valid: true)
@valid = valid
def self.loading
new([], status: :loading)
end
def initialize(deployments, status: :found)
@status = status
@deployments = deployments
@instances = deployments.flat_map(&:instances)
......
require 'spec_helper'
describe Projects::EnvironmentsController do
include KubernetesHelpers
set(:user) { create(:user) }
set(:project) { create(:project) }
......@@ -21,11 +23,19 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(:ok)
end
it 'expires etag cache to force reload environments list' do
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch).with(project_environments_path(project, format: :json))
get :index, environment_params
end
end
context 'when requesting JSON response for folders' do
before do
allow_any_instance_of(Environment).to receive(:deployment_service_ready?).and_return(true)
allow_any_instance_of(Environment).to receive(:rollout_status).and_return(kube_deployment_rollout_status)
create(:environment, project: project,
name: 'staging/review-1',
......@@ -49,14 +59,18 @@ describe Projects::EnvironmentsController do
get :index, environment_params(format: :json, scope: :available)
end
it 'responds with matching schema' do
expect(response).to match_response_schema('environments')
end
it 'responds with a payload describing available environments' do
expect(environments.count).to eq 2
expect(environments.first['name']).to eq 'production'
expect(environments.first['latest']['rollout_status_path']).to be_present
expect(environments.first['latest']['rollout_status']).to be_present
expect(environments.second['name']).to eq 'staging'
expect(environments.second['size']).to eq 2
expect(environments.second['latest']['name']).to eq 'staging/review-2'
expect(environments.second['latest']['rollout_status_path']).to be_present
expect(environments.second['latest']['rollout_status']).to be_present
end
it 'contains values describing environment scopes sizes' do
......@@ -96,8 +110,8 @@ describe Projects::EnvironmentsController do
end
it 'does not return the rollout_status_path attribute' do
expect(environments.first['latest']['rollout_status_path']).to be_blank
expect(environments.second['latest']['rollout_status_path']).to be_blank
expect(environments.first['latest']['rollout_status']).not_to be_present
expect(environments.second['latest']['rollout_status']).not_to be_present
end
end
end
......@@ -289,59 +303,6 @@ describe Projects::EnvironmentsController do
end
end
describe 'GET #status' do
context 'without deployment service' do
it 'returns 404' do
get :status, environment_params
expect(response.status).to eq(404)
end
end
context 'with deployment service' do
let(:project) { create(:kubernetes_project) }
let(:environment) { create(:environment, name: 'production', project: project) }
before do
stub_licensed_features(deploy_board: true)
allow_any_instance_of(Environment).to receive(:deployment_service_ready?).and_return(true)
end
it 'returns 204 until the rollout status is present' do
expect_any_instance_of(Environment)
.to receive(:rollout_status)
.and_return(nil)
get :status, environment_params
expect(response.status).to eq(204)
expect(response.headers['Poll-Interval']).to eq("3000")
end
it 'returns the rollout status when present' do
expect_any_instance_of(Environment)
.to receive(:rollout_status)
.and_return(::Gitlab::Kubernetes::RolloutStatus.new([]))
get :status, environment_params
expect(response.status).to eq(200)
end
end
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
stub_licensed_features(deploy_board: false)
end
it 'does not return any data' do
get :status, environment_params
expect(response).to have_http_status(:not_found)
end
end
end
describe 'GET #metrics' do
before do
allow(controller).to receive(:environment).and_return(environment)
......
......@@ -9,6 +9,7 @@ describe KubernetesService, models: true, use_clean_rails_memory_store_caching:
describe '#rollout_status' do
let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
subject(:rollout_status) { service.rollout_status(environment) }
context 'with valid deployments' do
......@@ -24,5 +25,30 @@ describe KubernetesService, models: true, use_clean_rails_memory_store_caching:
expect(rollout_status.deployments.map(&:labels)).to eq([{ 'app' => 'env-000000' }])
end
end
context 'with empty list of deployments' do
before do
stub_reactive_cache(
service,
deployments: []
)
end
it 'creates a matching RolloutStatus' do
expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
expect(rollout_status).to be_not_found
end
end
context 'not yet loaded deployments' do
before do
stub_reactive_cache
end
it 'creates a matching RolloutStatus' do
expect(rollout_status).to be_kind_of(::Gitlab::Kubernetes::RolloutStatus)
expect(rollout_status).to be_loading
end
end
end
end
{
"additionalProperties": false,
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"iid": {
"type": "integer"
},
"last?": {
"type": "boolean"
},
"ref": {
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"sha": {
"type": "string"
},
"tag": {
"type": "boolean"
}
},
"required": [
"sha",
"created_at",
"iid",
"tag",
"last?",
"ref",
"id"
],
"type": "object"
}
......@@ -3,49 +3,7 @@
"properties": {
"deployments": {
"items": {
"additionalProperties": false,
"properties": {
"created_at": {
"type": "string"
},
"id": {
"type": "integer"
},
"iid": {
"type": "integer"
},
"last?": {
"type": "boolean"
},
"ref": {
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"sha": {
"type": "string"
},
"tag": {
"type": "boolean"
}
},
"required": [
"sha",
"created_at",
"iid",
"tag",
"last?",
"ref",
"id"
],
"type": "object"
"$ref": "deployment.json"
},
"minItems": 1,
"type": "array"
......
{
"type": "object",
"additionalProperties": false,
"required": [
"id",
"name",
"state",
"last_deployment",
"environment_path",
"created_at",
"updated_at"
],
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"state": {
"type": "string"
},
"external_url": {
"type": "string"
},
"environment_type": {
"type": [
"string",
"null"
]
},
"last_deployment": {
"oneOf": [
{
"$ref": "deployment.json"
},
{
"type": ["null"]
}
]
},
"stop_action?": {
"type": "boolean"
},
"rollout_status": {
"$ref": "rollout_status.json"
},
"environment_path": {
"type": "string"
},
"stop_path": {
"type": "string"
},
"terminal_path": {
"type": "string"
},
"folder_path": {
"type": "string"
},
"created_at": {
"type": "date"
},
"updated_at": {
"type": "date"
}
}
}
{
"additionalProperties": false,
"properties": {
"environments": {
"items": {
"$ref": "environments_group.json"
},
"minItems": 1,
"type": "array"
},
"available_count": {
"type": "integer"
},
"stopped_count": {
"type": "integer"
}
},
"required": [
"environments",
"available_count",
"stopped_count"
],
"type": "object"
}
{
"type": "object",
"required": [
"name",
"size",
"latest"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"size": {
"type": "integer"
},
"latest": {
"$ref": "environment.json"
}
}
}
{
"type": "object",
"additionalProperties": false,
"required": [
"status"
],
"properties": {
"status": {
"type": "string"
},
"completion": {
"type": "integer"
},
"is_completed": {
"type": "boolean"
},
"instances": {
"type": "array",
"items": {
"additionalProperties": false,
"type": "object",
"required": [
"status",
"tooltip",
"track",
"stable"
],
"properties": {
"status": {
"type": "string"
},
"tooltip": {
"type": "string"
},
"track": {
"type": "string"
},
"stable": {
"type": "boolean"
}
}
}
}
}
}
import Vue from 'vue';
import DeployBoard from '~/environments/components/deploy_board_component.vue';
import { deployBoardMockData, invalidDeployBoardMockData } from './mock_data';
import { deployBoardMockData } from './mock_data';
describe('Deploy Board', () => {
let DeployBoardComponent;
......@@ -17,7 +17,7 @@ describe('Deploy Board', () => {
propsData: {
deployBoardData: deployBoardMockData,
isLoading: false,
hasError: false,
isEmpty: false,
},
}).$mount();
});
......@@ -46,15 +46,15 @@ describe('Deploy Board', () => {
});
});
describe('without valid data', () => {
describe('with empty state', () => {
let component;
beforeEach(() => {
component = new DeployBoardComponent({
propsData: {
deployBoardData: invalidDeployBoardMockData,
deployBoardData: {},
isLoading: false,
hasError: false,
isEmpty: true,
},
}).$mount();
});
......@@ -65,21 +65,21 @@ describe('Deploy Board', () => {
});
});
describe('with error', () => {
describe('with loading state', () => {
let component;
beforeEach(() => {
component = new DeployBoardComponent({
propsData: {
deployBoardData: {},
isLoading: false,
hasError: true,
isLoading: true,
isEmpty: false,
},
}).$mount();
});
it('should render empty state', () => {
expect(component.$el.children.length).toEqual(1);
it('should render loading spinner', () => {
expect(component.$el.querySelector('.fa-spin')).toBeDefined();
});
});
});
......@@ -37,12 +37,11 @@ describe('Environment item', () => {
size: 1,
environment_path: 'url',
id: 1,
rollout_status_path: 'url',
hasDeployBoard: true,
deployBoardData: deployBoardMockData,
isDeployBoardVisible: true,
isLoadingDeployBoard: false,
hasErrorDeployBoard: false,
isEmptyDeployBoard: false,
};
const component = new EnvironmentTable({
......@@ -66,7 +65,6 @@ describe('Environment item', () => {
size: 1,
environment_path: 'url',
id: 1,
rollout_status_path: 'url',
hasDeployBoard: true,
deployBoardData: {
instances: [
......
......@@ -29,12 +29,12 @@ describe('Store', () => {
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
updated_at: '2017-01-31T10:53:46.894Z',
rollout_status_path: '/path',
rollout_status: {},
hasDeployBoard: true,
isDeployBoardVisible: false,
isDeployBoardVisible: true,
deployBoardData: {},
isLoadingDeployBoard: false,
hasErrorDeployBoard: false,
isEmptyDeployBoard: false,
};
store.storeEnvironments(serverData);
......@@ -58,20 +58,20 @@ describe('Store', () => {
expect(store.state.environments.length).toEqual(serverData.length);
});
it('should store a non folder environment with deploy board if rollout_status_path key is provided', () => {
it('should store a non folder environment with deploy board if rollout_status key is provided', () => {
const environment = {
name: 'foo',
size: 1,
latest: {
id: 1,
rollout_status_path: 'url',
rollout_status: deployBoardMockData,
},
};
store.storeEnvironments([environment]);
expect(store.state.environments[0].hasDeployBoard).toEqual(true);
expect(store.state.environments[0].isDeployBoardVisible).toEqual(false);
expect(store.state.environments[0].deployBoardData).toEqual({});
expect(store.state.environments[0].isDeployBoardVisible).toEqual(true);
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
});
it('should add folder keys when environment is a folder', () => {
......@@ -192,7 +192,7 @@ describe('Store', () => {
latest: {
id: 1,
},
rollout_status_path: 'path',
rollout_status: deployBoardMockData,
};
store.storeEnvironments([environment]);
......@@ -201,16 +201,10 @@ describe('Store', () => {
it('should toggle deploy board property for given environment id', () => {
store.toggleDeployBoard(1);
expect(store.state.environments[0].isDeployBoardVisible).toEqual(true);
});
it('should store deploy board data for given environment id', () => {
store.storeDeployBoard(1, deployBoardMockData);
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
expect(store.state.environments[0].isDeployBoardVisible).toEqual(false);
});
it('should keep deploy board data when updating environments', () => {
store.storeDeployBoard(1, deployBoardMockData);
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
const environment = {
......@@ -219,7 +213,7 @@ describe('Store', () => {
latest: {
id: 1,
},
rollout_status_path: 'path',
rollout_status: deployBoardMockData,
};
store.storeEnvironments([environment]);
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
......@@ -243,12 +237,12 @@ describe('Store', () => {
latest: {
id: 1,
},
rollout_status_path: 'path',
rollout_status: deployBoardMockData,
};
store.storeEnvironments([environment]);
expect(store.getOpenDeployBoards().length).toEqual(0);
expect(store.getOpenDeployBoards().length).toEqual(1);
});
});
});
......@@ -12,7 +12,7 @@ export const environmentsList = [
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
updated_at: '2017-01-31T10:53:46.894Z',
rollout_status_path: '/path',
rollout_status: {},
},
{
folderName: 'build',
......@@ -28,7 +28,7 @@ export const environmentsList = [
stop_path: '/root/review-app/environments/12/stop',
created_at: '2017-02-01T19:42:18.400Z',
updated_at: '2017-02-01T19:42:18.400Z',
rollout_status_path: '/path',
rollout_status: {},
},
];
......@@ -48,7 +48,7 @@ export const serverData = [
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
updated_at: '2017-01-31T10:53:46.894Z',
rollout_status_path: '/path',
rollout_status: {},
},
},
{
......@@ -100,7 +100,7 @@ export const environment = {
stop_path: '/root/review-app/environments/7/stop',
created_at: '2017-01-31T10:53:46.894Z',
updated_at: '2017-01-31T10:53:46.894Z',
rollout_status_path: '/path',
rollout_status: {},
};
export const deployBoardMockData = {
......@@ -136,15 +136,7 @@ export const deployBoardMockData = {
abort_url: 'url',
rollback_url: 'url',
completion: 100,
valid: true,
};
export const invalidDeployBoardMockData = {
instances: [],
abort_url: 'url',
rollback_url: 'url',
completion: 100,
valid: false,
status: 'found',
};
export const folder = {
......
......@@ -94,15 +94,33 @@ describe Gitlab::Kubernetes::RolloutStatus do
end
end
describe '#valid?' do
describe '#not_found?' do
context 'when the specs are passed' do
it { is_expected.to be_valid }
it { is_expected.not_to be_not_found }
end
context 'when no specs are passed' do
context 'when list of specs is empty' do
let(:specs) { specs_none }
it { is_expected.not_to be_valid }
it { is_expected.to be_not_found }
end
end
describe '#found?' do
context 'when the specs are passed' do
it { is_expected.to be_found }
end
context 'when list of specs is empty' do
let(:specs) { specs_none }
it { is_expected.not_to be_found }
end
end
describe '.loading' do
subject { described_class.loading }
it { is_expected.to be_loading }
end
end
......@@ -115,6 +115,13 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
go!
end
it "calls a reactive_cache_updated only once if content did not change on subsequent update" do
expect(instance).to receive(:calculate_reactive_cache).twice
expect(instance).to receive(:reactive_cache_updated).once
2.times { instance.exclusively_update_reactive_cache! }
end
context 'and #calculate_reactive_cache raises an exception' do
before do
stub_reactive_cache(instance, "preexisting")
......
......@@ -385,6 +385,7 @@ describe Environment do
describe '#rollout_status' do
let(:project) { create(:kubernetes_project) }
subject { environment.rollout_status }
context 'when the environment has rollout status' do
......
require 'spec_helper'
describe EnvironmentEntity do
include KubernetesHelpers
let(:user) { create(:user) }
let(:environment) { create(:environment) }
......@@ -50,12 +52,11 @@ describe EnvironmentEntity do
before do
stub_licensed_features(deploy_board: true)
allow(environment).to receive(:deployment_service_ready?).and_return(true)
allow(environment).to receive(:rollout_status).and_return(kube_deployment_rollout_status)
end
it 'exposes rollout_status_path' do
expected = '/' + [environment.project.full_path, 'environments', environment.id, 'status.json'].join('/')
expect(subject[:rollout_status_path]).to eq(expected)
it 'exposes rollout_status' do
expect(subject).to include(:rollout_status)
end
end
......@@ -65,7 +66,7 @@ describe EnvironmentEntity do
allow(environment).to receive(:deployment_service_ready?).and_return(true)
end
it 'does not expose rollout_status_path' do
it 'does not expose rollout_status' do
expect(subject[:rollout_status_path]).to be_blank
end
end
......
......@@ -54,7 +54,7 @@ describe EnvironmentSerializer do
context 'when representing environments within folders' do
let(:serializer) do
described_class.new(project: project).within_folders
described_class.new(current_user: user, project: project).within_folders
end
let(:resource) { Environment.all }
......@@ -123,7 +123,7 @@ describe EnvironmentSerializer do
let(:pagination) { { page: 1, per_page: 2 } }
let(:serializer) do
described_class.new(project: project)
described_class.new(current_user: user, project: project)
.with_pagination(request, response)
end
......@@ -169,7 +169,7 @@ describe EnvironmentSerializer do
context 'when grouping environments within folders' do
let(:serializer) do
described_class.new(project: project)
described_class.new(current_user: user, project: project)
.with_pagination(request, response)
.within_folders
end
......
......@@ -7,11 +7,29 @@ describe RolloutStatusEntity do
described_class.new(rollout_status, request: double)
end
let(:rollout_status) { ::Gitlab::Kubernetes::RolloutStatus.from_specs(kube_deployment) }
subject { entity.as_json }
it { is_expected.to have_key(:instances) }
it { is_expected.to have_key(:completion) }
it { is_expected.to have_key(:is_completed) }
it { is_expected.to have_key(:valid) }
context 'when kube deployment is valid' do
let(:rollout_status) { kube_deployment_rollout_status }
it "exposes status" do
is_expected.to include(:status)
end
it "exposes deployment data" do
is_expected.to include(:instances, :completion, :is_completed)
end
end
context 'when kube deployment is empty' do
let(:rollout_status) { empty_deployment_rollout_status }
it "exposes status" do
is_expected.to include(:status)
end
it "does not expose deployment data" do
is_expected.not_to include(:instances, :completion, :is_completed)
end
end
end
......@@ -122,4 +122,12 @@ module KubernetesHelpers
terminal
end
end
def kube_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_specs(kube_deployment)
end
def empty_deployment_rollout_status
::Gitlab::Kubernetes::RolloutStatus.from_specs()
end
end
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