Commit ec3044ac authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into rc/ce-to-ee-wednesday

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents a79fccf0 cecdaaca
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 9.0.4 (2017-04-05)
- No changes.
## 9.0.3 (2017-04-05) ## 9.0.3 (2017-04-05)
- Allow to edit pipelines quota for user. - Allow to edit pipelines quota for user.
...@@ -60,6 +64,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -60,6 +64,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- [Elasticsearch] More efficient search. - [Elasticsearch] More efficient search.
- Get Geo secondaries nodes statuses over AJAX. - Get Geo secondaries nodes statuses over AJAX.
## 8.17.5 (2017-04-05)
- No changes.
## 8.17.4 (2017-03-19) ## 8.17.4 (2017-03-19)
- Elastic security fix: Respect feature visibility level. - Elastic security fix: Respect feature visibility level.
...@@ -98,6 +106,10 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -98,6 +106,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Reduce queries needed to check if node is a primary or secondary Geo node. - Reduce queries needed to check if node is a primary or secondary Geo node.
- Allow squashing merge requests into a single commit. - Allow squashing merge requests into a single commit.
## 8.16.9 (2017-04-05)
- No changes.
## 8.16.8 (2017-03-19) ## 8.16.8 (2017-03-19)
- No changes. - No changes.
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 9.0.4 (2017-04-05)
- Don’t show source project name when user does not have access.
- Remove the class attribute from the whitelist for HTML generated from Markdown.
- Fix path disclosure in project import/export.
- Fix for open redirect vulnerability using continue[to] in URL when requesting project import status.
- Fix for open redirect vulnerabilities in todos, issues, and MR controllers.
## 9.0.3 (2017-04-05) ## 9.0.3 (2017-04-05)
- Fix name colision when importing GitHub pull requests from forked repositories. !9719 - Fix name colision when importing GitHub pull requests from forked repositories. !9719
...@@ -320,6 +328,14 @@ entry. ...@@ -320,6 +328,14 @@ entry.
- Change development tanuki favicon colors to match logo color order. - Change development tanuki favicon colors to match logo color order.
- API issues - support filtering by iids. - API issues - support filtering by iids.
## 8.17.5 (2017-04-05)
- Don’t show source project name when user does not have access.
- Remove the class attribute from the whitelist for HTML generated from Markdown.
- Fix path disclosure in project import/export.
- Fix for open redirect vulnerability using continue[to] in URL when requesting project import status.
- Fix for open redirect vulnerabilities in todos, issues, and MR controllers.
## 8.17.4 (2017-03-19) ## 8.17.4 (2017-03-19)
- Only show public emails in atom feeds. - Only show public emails in atom feeds.
...@@ -534,6 +550,14 @@ entry. ...@@ -534,6 +550,14 @@ entry.
- Remove deprecated GitlabCiService. - Remove deprecated GitlabCiService.
- Requeue pending deletion projects. - Requeue pending deletion projects.
## 8.16.9 (2017-04-05)
- Don’t show source project name when user does not have access.
- Remove the class attribute from the whitelist for HTML generated from Markdown.
- Fix path disclosure in project import/export.
- Fix for open redirect vulnerability using continue[to] in URL when requesting project import status.
- Fix for open redirect vulnerabilities in todos, issues, and MR controllers.
## 8.16.8 (2017-03-19) ## 8.16.8 (2017-03-19)
- Only show public emails in atom feeds. - Only show public emails in atom feeds.
......
This diff is collapsed.
import Cookies from 'js-cookie';
import BurndownChart from './burndown_chart';
$(() => {
// handle hint dismissal
const hint = $('.burndown-hint');
hint.on('click', '.dismiss-icon', () => {
hint.hide();
Cookies.set('hide_burndown_message', 'true');
});
// generate burndown chart (if data available)
const container = '.burndown-chart';
const $chartElm = $(container);
if ($chartElm.length) {
const startDate = $chartElm.data('startDate');
const dueDate = $chartElm.data('dueDate');
const chartData = $chartElm.data('chartData');
const openIssuesCount = chartData.map(d => [d[0], d[1]]);
const openIssuesWeight = chartData.map(d => [d[0], d[2]]);
const chart = new BurndownChart({ container, startDate, dueDate });
let currentView = 'count';
chart.setData(openIssuesCount, { label: 'Open issues', animate: true });
$('.js-burndown-data-selector').on('click', 'button', function switchData() {
const $this = $(this);
const show = $this.data('show');
if (currentView !== show) {
currentView = show;
$this.addClass('active').siblings().removeClass('active');
switch (show) {
case 'count':
chart.setData(openIssuesCount, { label: 'Open issues', animate: true });
break;
case 'weight':
chart.setData(openIssuesWeight, { label: 'Open issue weight', animate: true });
break;
default:
break;
}
}
});
window.addEventListener('resize', () => chart.animateResize(1));
$(document).on('click', '.js-sidebar-toggle', () => chart.animateResize(2));
}
});
...@@ -163,7 +163,8 @@ export default { ...@@ -163,7 +163,8 @@ export default {
<template v-for="instance in deployBoardData.instances"> <template v-for="instance in deployBoardData.instances">
<instance-component <instance-component
:status="instance.status" :status="instance.status"
:tooltipText="instance.tooltip"/> :tooltip-text="instance.tooltip"
:stable="instance.stable" />
</template> </template>
</div> </div>
</section> </section>
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
* see more information about this in * see more information about this in
* https://gitlab.com/gitlab-org/gitlab-ee/uploads/5fff049fd88336d9ee0c6ef77b1ba7e3/monitoring__deployboard--key.png * https://gitlab.com/gitlab-org/gitlab-ee/uploads/5fff049fd88336d9ee0c6ef77b1ba7e3/monitoring__deployboard--key.png
* *
* An instance can represent a normal deploy or a canary deploy. In the latter we need to provide
* this information in the tooltip and the colors.
* Mockup is https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1551#note_26595150
*/ */
export default { export default {
...@@ -28,11 +31,23 @@ export default { ...@@ -28,11 +31,23 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
stable: {
type: Boolean,
required: false,
default: true,
},
}, },
computed: { computed: {
cssClass() { cssClass() {
return `deploy-board-instance-${this.status}`; let cssClassName = `deploy-board-instance-${this.status}`;
if (!this.stable) {
cssClassName = `${cssClassName} deploy-board-instance-canary`;
}
return cssClassName;
}, },
}, },
......
...@@ -240,6 +240,9 @@ ...@@ -240,6 +240,9 @@
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
margin: 1px; margin: 1px;
display: flex;
justify-content: center;
align-items: center;
&-finished { &-finished {
background-color: $green-100; background-color: $green-100;
...@@ -270,6 +273,17 @@ ...@@ -270,6 +273,17 @@
background-color: $white-light; background-color: $white-light;
border-color: $border-color; border-color: $border-color;
} }
&.deploy-board-instance-canary {
&::after {
width: 7px;
height: 7px;
border: 1px solid $white-light;
background-color: $orange-300;
border-radius: 50%;
content: "";
}
}
} }
.deploy-board-icon i { .deploy-board-icon i {
......
...@@ -200,3 +200,157 @@ ...@@ -200,3 +200,157 @@
cursor: -webkit-grab; cursor: -webkit-grab;
cursor: grab; cursor: grab;
} }
// EE-only
.burndown-hint.container-fluid {
border: 1px solid $border-color;
border-radius: $border-radius-default;
position: relative;
margin: $gl-padding 0;
overflow: hidden;
padding-top: 15px;
padding-bottom: 15px;
.dismiss-icon {
position: absolute;
right: $gl-padding;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
z-index: 1;
}
.svg-container {
text-align: center;
svg {
max-width: 200px;
max-height: 200px;
}
}
.inner-content {
@media (max-width: $screen-xs-max) {
padding: 0 28px;
text-align: center;
}
h4 {
color: $gl-text-color;
font-size: 17px;
}
p {
color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding;
}
}
}
.burndown-header {
margin: 24px 0 12px;
h3 {
font-size: 16px;
margin: 0;
}
.btn-group {
margin-left: 20px;
margin-bottom: 2px;
}
.btn {
font-size: 12px;
@include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
&.active {
background-color: $blue-500;
border-color: $blue-600;
color: $white-light;
}
}
}
.burndown-chart {
width: 100%;
height: 380px;
margin: 5px 0;
@media (max-width: $screen-sm-max) {
height: 320px;
}
@media (max-width: $screen-xs-max) {
height: 200px;
}
.axis {
font-size: 12px;
line,
path {
fill: none;
stroke: $stat-graph-axis-fill;
shape-rendering: crispEdges;
}
}
.axis-label {
text {
fill: $gl-text-color-secondary;
}
line {
stroke: $border-color;
}
}
.legend {
shape-rendering: crispEdges;
text {
font-size: 13px;
fill: $gl-text-color-disabled;
}
rect {
stroke: $border-color;
fill: none;
}
}
.line {
stroke-width: 2px;
fill: none;
&.actual {
stroke: $gl-success;
}
&.ideal {
stroke: $stat-graph-axis-fill;
stroke-dasharray: 6px 6px;
}
}
.focus {
circle {
fill: $white-light;
stroke: $gl-success;
stroke-width: 2px;
}
}
.chart-tooltip {
text {
font-size: 12px;
fill: $white-light;
}
rect {
fill: $black;
}
}
}
class Projects::EnvironmentsController < Projects::ApplicationController class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project' layout 'project'
before_action :authorize_read_environment! 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_environment!, only: [:new, :create]
before_action :authorize_create_deployment!, only: [:stop] before_action :authorize_create_deployment!, only: [:stop]
before_action :authorize_update_environment!, only: [:edit, :update] before_action :authorize_update_environment!, only: [:edit, :update]
......
...@@ -77,11 +77,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -77,11 +77,6 @@ class Projects::IssuesController < Projects::ApplicationController
@merge_request_to_resolve_discussions_of = service.merge_request_to_resolve_discussions_of @merge_request_to_resolve_discussions_of = service.merge_request_to_resolve_discussions_of
@discussion_to_resolve = service.discussions_to_resolve.first if params[:discussion_to_resolve] @discussion_to_resolve = service.discussions_to_resolve.first if params[:discussion_to_resolve]
# Set Issue description based on project template
if @project.issues_template.present?
@issue.description = @project.issues_template
end
respond_with(@issue) respond_with(@issue)
end end
......
...@@ -42,6 +42,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -42,6 +42,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def show def show
@burndown = Burndown.new(@milestone)
end end
def create def create
......
class Burndown
attr_accessor :start_date, :due_date, :end_date, :issues_count, :issues_weight
def initialize(milestone)
@milestone = milestone
@start_date = @milestone.start_date
@due_date = @milestone.due_date
@end_date = @milestone.due_date
@end_date = Date.today if @end_date.present? && @end_date > Date.today
@issues_count, @issues_weight = milestone.issues.reorder(nil).pluck('COUNT(*), COALESCE(SUM(weight), 0)').first
end
# Returns the chart data in the following format:
# [date, issue count, issue weight] eg: [["2017-03-01", 33, 127], ["2017-03-02", 35, 73], ["2017-03-03", 28, 50]...]
def as_json(opts = nil)
return [] unless valid?
open_issues_count = issues_count
open_issues_weight = issues_weight
start_date.upto(end_date).each_with_object([]) do |date, chart_data|
closed, reopened = closed_and_reopened_issues_by(date)
closed_issues_count = closed.count
closed_issues_weight = sum_issues_weight(closed)
open_issues_count -= closed_issues_count
open_issues_weight -= closed_issues_weight
chart_data << [date.strftime("%Y-%m-%d"), open_issues_count, open_issues_weight]
reopened_count = reopened.count
reopened_weight = sum_issues_weight(reopened)
open_issues_count += reopened_count
open_issues_weight += reopened_weight
end
end
def valid?
start_date && due_date
end
private
def sum_issues_weight(issues)
issues.map(&:weight).compact.sum
end
def closed_and_reopened_issues_by(date)
current_date = date.to_date
closed = issues_with_closed_at.select { |issue| issue.closed_at.to_date == current_date }
reopened = closed.select { |issue| issue.state == 'reopened' }
[closed, reopened]
end
def issues_with_closed_at
@issues_with_closed_at ||=
@milestone.issues.select('closed_at, weight, state').
where('closed_at IS NOT NULL').
order('closed_at ASC')
end
end
...@@ -15,4 +15,19 @@ class MockDeploymentService < DeploymentService ...@@ -15,4 +15,19 @@ class MockDeploymentService < DeploymentService
def terminals(environment) def terminals(environment)
[] []
end end
def rollout_status(environment)
OpenStruct.new(
instances: rollout_status_instances,
completion: 80,
valid?: true,
complete?: true
)
end
private
def rollout_status_instances
JSON.parse(Rails.root.join('spec', 'fixtures', 'rollout_status_instances.json'))
end
end end
...@@ -12,6 +12,6 @@ class MockMonitoringService < MonitoringService ...@@ -12,6 +12,6 @@ class MockMonitoringService < MonitoringService
end end
def metrics(environment) def metrics(environment)
JSON.parse(File.read(Rails.root + 'spec/fixtures/metrics.json')) JSON.parse(Rails.root.join('spec', 'fixtures', 'metrics.json'))
end end
end end
...@@ -73,6 +73,10 @@ class ProjectPolicy < BasePolicy ...@@ -73,6 +73,10 @@ class ProjectPolicy < BasePolicy
can! :read_environment can! :read_environment
can! :read_deployment can! :read_deployment
can! :read_merge_request can! :read_merge_request
if License.current&.add_on?('GitLab_DeployBoard')
can! :read_deploy_board
end
end end
# Permissions given when an user is team member of a project # Permissions given when an user is team member of a project
......
...@@ -39,6 +39,7 @@ class EnvironmentEntity < Grape::Entity ...@@ -39,6 +39,7 @@ class EnvironmentEntity < Grape::Entity
end end
expose :rollout_status_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment| expose :rollout_status_path, if: ->(environment, _) { environment.deployment_service_ready? } do |environment|
can?(request.user, :read_deploy_board, environment.project) &&
status_namespace_project_environment_path( status_namespace_project_environment_path(
environment.project.namespace, environment.project.namespace,
environment.project, environment.project,
......
...@@ -7,6 +7,10 @@ module Issues ...@@ -7,6 +7,10 @@ module Issues
@issue = project.issues.new(issue_params) @issue = project.issues.new(issue_params)
end end
def issue_params_from_template
{ description: project.issues_template }
end
def issue_params_with_info_from_discussions def issue_params_with_info_from_discussions
return {} unless merge_request_to_resolve_discussions_of return {} unless merge_request_to_resolve_discussions_of
...@@ -49,8 +53,13 @@ module Issues ...@@ -49,8 +53,13 @@ module Issues
[discussion_info, quote].join("\n\n") [discussion_info, quote].join("\n\n")
end end
# Issue params can be built from 3 types of passed params,
# They take precedence over eachother like this
# passed params > discussion params > template params
def issue_params def issue_params
@issue_params ||= issue_params_with_info_from_discussions.merge(whitelisted_issue_params) @issue_params ||= issue_params_from_template.
merge(issue_params_with_info_from_discussions).
merge(whitelisted_issue_params)
end end
def whitelisted_issue_params def whitelisted_issue_params
......
...@@ -46,6 +46,8 @@ ...@@ -46,6 +46,8 @@
= preserve do = preserve do
= markdown_field(@milestone, :description) = markdown_field(@milestone, :description)
= render 'shared/milestones/burndown', milestone: @milestone, project: @project, burndown: @burndown
- if can?(current_user, :read_issue, @project) && @milestone.total_items_count(current_user).zero? - if can?(current_user, :read_issue, @project) && @milestone.total_items_count(current_user).zero?
.alert.alert-success.prepend-top-default .alert.alert-success.prepend-top-default
%span Assign some issues to this milestone. %span Assign some issues to this milestone.
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 158"><g fill="none" fill-rule="evenodd" transform="translate(-48-26)"><path fill="#fff" d="m25 28h240v158h-240z"/><g transform="translate(56 37)"><g transform="translate(0 21)"><path fill="#eee" d="m13.591 92.21l3.03-3.979c.669-.879.499-2.134-.38-2.803-.879-.669-2.134-.499-2.803.38l-3.03 3.979c-.669.879-.499 2.134.38 2.803.879.669 2.134.499 2.803-.38m10.902-14.323l3.03-3.979c.669-.879.499-2.134-.38-2.803-.879-.669-2.134-.499-2.803.38l-3.03 3.979c-.669.879-.499 2.134.38 2.803.879.669 2.134.499 2.803-.38m10.902-14.323l3.03-3.979c.669-.879.499-2.134-.38-2.803-.879-.669-2.134-.499-2.803.38l-3.03 3.979c-.669.879-.499 2.134.38 2.803.879.669 2.134.499 2.803-.38m10.902-14.323l3.03-3.979c.669-.879.499-2.134-.38-2.803-.879-.669-2.134-.499-2.803.38l-3.03 3.979c-.669.879-.499 2.134.38 2.803.879.669 2.134.499 2.803-.38m10.902-14.323l3.03-3.979c.669-.879.499-2.134-.38-2.803-.879-.669-2.134-.499-2.803.38l-3.03 3.979c-.669.879-.499 2.134.38 2.803.879.669 2.134.499 2.803-.38m7.167-4.942l2.644 4.244c.584.938 1.818 1.224 2.755.64.938-.584 1.224-1.818.64-2.755l-2.644-4.244c-.584-.938-1.818-1.224-2.755-.64-.938.584-1.224 1.818-.64 2.755m9.517 15.278l2.644 4.244c.584.938 1.818 1.224 2.755.64.938-.584 1.224-1.818.64-2.755l-2.644-4.244c-.584-.938-1.818-1.224-2.755-.64-.938.584-1.224 1.818-.64 2.755m9.517 15.278l2.644 4.244c.584.938 1.818 1.224 2.755.64.938-.584 1.224-1.818.64-2.755l-2.644-4.244c-.584-.938-1.818-1.224-2.755-.64-.938.584-1.224 1.818-.64 2.755m9.517 15.278l2.644 4.244c.584.938 1.818 1.224 2.755.64.938-.584 1.224-1.818.64-2.755l-2.644-4.244c-.584-.938-1.818-1.224-2.755-.64-.938.584-1.224 1.818-.64 2.755m13.423 5.929l3.213-3.831c.71-.846.599-2.108-.247-2.818-.846-.71-2.108-.599-2.818.247l-3.213 3.831c-.71.846-.599 2.108.247 2.818.846.71 2.108.599 2.818-.247m11.567-13.791l3.213-3.831c.71-.846.599-2.108-.247-2.818-.846-.71-2.108-.599-2.818.247l-3.213 3.831c-.71.846-.599 2.108.247 2.818.846.71 2.108.599 2.818-.247m8.253-11.614l2.794 4.146c.617.916 1.86 1.158 2.776.541.916-.617 1.158-1.86.541-2.776l-2.794-4.146c-.617-.916-1.86-1.158-2.776-.541-.916.617-1.158 1.86-.541 2.776m10.06 14.927l2.794 4.146c.617.916 1.86 1.158 2.776.541.916-.617 1.158-1.86.541-2.776l-2.794-4.146c-.617-.916-1.86-1.158-2.776-.541-.916.617-1.158 1.86-.541 2.776m10.06 14.927l2.794 4.146c.617.916 1.86 1.158 2.776.541.916-.617 1.158-1.86.541-2.776l-2.794-4.146c-.617-.916-1.86-1.158-2.776-.541-.916.617-1.158 1.86-.541 2.776m10.06 14.927c.618.917 1.861 1.159 2.777.541.916-.617 1.158-1.86.541-2.776-.618-.917-1.861-1.159-2.777-.541-.916.617-1.158 1.86-.541 2.776"/><g transform="translate(61)"><rect width="3" height="24" fill="#fde5d8" rx="1.5"/><path fill="#fc6d26" d="m3 13v-11l11.533 3.105c1.387.373 1.478 1.207.192 1.868l-11.724 6.03"/></g><path fill="#b5a7dd" d="m166.93 83.6l-13.994-7.365c-.287-.151-.607-.23-.931-.23h-20.485c-.491-.271-.816-.45-4.08-2.251l-24.469-13.5c-.694-.383-1.548-.32-2.178.16l-19.433 14.806-20.783-26.451c-.65-.827-1.83-1.01-2.699-.417l-20.604 14.05-20.904-21.708c-.614 1.222-1.633 2.206-2.881 2.775l22.08 22.924c.677.703 1.761.815 2.567.265l20.456-13.947 20.845 26.53c.675.859 1.915 1.018 2.785.355l19.963-15.21c-.005-.003.678.374 3.39 1.87l24.469 13.5c.296.163.628.249.966.249h20.506l13.556 7.135c.201-1.391.879-2.628 1.864-3.539"/><path fill="#6b4fbb" d="m171 96c4.418 0 8-3.582 8-8 0-4.418-3.582-8-8-8-4.418 0-8 3.582-8 8 0 4.418 3.582 8 8 8m0-4c-2.209 0-4-1.791-4-4 0-2.209 1.791-4 4-4 2.209 0 4 1.791 4 4 0 2.209-1.791 4-4 4m-160-46c4.418 0 8-3.582 8-8 0-4.418-3.582-8-8-8-4.418 0-8 3.582-8 8 0 4.418 3.582 8 8 8m0-4c-2.209 0-4-1.791-4-4 0-2.209 1.791-4 4-4 2.209 0 4 1.791 4 4 0 2.209-1.791 4-4 4"/></g><path fill="#fde5d8" d="m168.78 39.803l-2.908.646c-.542.12-.882-.228-.763-.763l.646-2.908-.646-2.908c-.12-.542.228-.882.763-.763l2.908.646 2.908-.646c.542-.12.882.228.763.763l-.646 2.908.646 2.908c.12.542-.228.882-.763.763l-2.908-.646" transform="matrix(.70711.70711-.70711.70711 75.44-108.57)"/><path fill="#d4cde8" d="m101.36 53.839l-2.21.491c-.537.119-.874-.226-.756-.756l.491-2.21-.491-2.21c-.119-.537.226-.874.756-.756l2.21.491 2.21-.491c.537-.119.874.226.756.756l-.491 2.21.491 2.21c.119.537-.226.874-.756.756l-2.21-.491" transform="matrix(.70711.70711-.70711.70711 66.01-56.631)"/><g fill="#fde5d8"><path d="m125.36 8.839l-2.21.491c-.537.119-.874-.226-.756-.756l.491-2.21-.491-2.21c-.119-.537.226-.874.756-.756l2.21.491 2.21-.491c.537-.119.874.226.756.756l-.491 2.21.491 2.21c.119.537-.226.874-.756.756l-2.21-.491" transform="matrix(.70711.70711-.70711.70711 41.22-86.78)"/><path d="m18.778 23.803l-2.908.646c-.542.12-.882-.228-.763-.763l.646-2.908-.646-2.908c-.12-.542.228-.882.763-.763l2.908.646 2.908-.646c.542-.12.882.228.763.763l-.646 2.908.646 2.908c.12.542-.228.882-.763.763l-2.908-.646" transform="matrix(.70711.70711-.70711.70711 20.19-7.192)"/></g></g></g></svg>
- milestone = local_assigns[:milestone]
- project = local_assigns[:project]
- burndown = local_assigns[:burndown]
- can_generate_chart = burndown&.valid?
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('burndown_chart')
- if can_generate_chart
.burndown-header
%h3
Burndown chart
.btn-group.js-burndown-data-selector
%button.btn.btn-xs.active{ data: { show: 'count' } }
Issues
%button.btn.btn-xs{ data: { show: 'weight' } }
Issue weight
.burndown-chart{ data: { start_date: burndown.start_date.strftime("%Y-%m-%d"), due_date: burndown.due_date.strftime("%Y-%m-%d"), chart_data: burndown.to_json } }
- elsif can?(current_user, :admin_milestone, @project) && cookies['hide_burndown_message'].nil?
.burndown-hint.content-block.container-fluid
= icon("times", class: "dismiss-icon")
.row
.col-sm-4.col-xs-12.svg-container
= custom_icon('icon_burndown_chart_splash')
.col-sm-8.col-xs-12.inner-content
%h4
Burndown chart
%p
View your milestone's progress as a burndown chart. Add both a start and a due date to
this milestone and the chart will appear here, always up-to-date.
= link_to "Add start and due date", edit_namespace_project_milestone_path(project.namespace, project, milestone), class: 'btn'
---
title: Added mock data for Deployboard
merge_request:
author:
---
title: Add burndown chart to milestones
merge_request:
author:
---
title: Add a Rake task to make the current node the primary Geo node
merge_request:
author:
---
title: Visualise Canary Deployments
merge_request:
author:
...@@ -23,6 +23,7 @@ var config = { ...@@ -23,6 +23,7 @@ var config = {
main: './main.js', main: './main.js',
blob: './blob_edit/blob_bundle.js', blob: './blob_edit/blob_bundle.js',
boards: './boards/boards_bundle.js', boards: './boards/boards_bundle.js',
burndown_chart: './burndown_chart/index.js',
cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js', cycle_analytics: './cycle_analytics/cycle_analytics_bundle.js',
commit_pipelines: './commit/pipelines/pipelines_bundle.js', commit_pipelines: './commit/pipelines/pipelines_bundle.js',
diff_notes: './diff_notes/diff_notes_bundle.js', diff_notes: './diff_notes/diff_notes_bundle.js',
...@@ -130,6 +131,7 @@ var config = { ...@@ -130,6 +131,7 @@ var config = {
'graphs', 'graphs',
'users', 'users',
'monitoring', 'monitoring',
'burndown_chart',
], ],
}), }),
......
require './spec/support/sidekiq'
require './spec/support/test_env'
class Gitlab::Seeder::Burndown
def initialize(project, perf: false)
@project = project
end
def seed!
Timecop.travel 10.days.ago
Sidekiq::Testing.inline! do
create_milestone
puts '.'
create_issues
puts '.'
close_issues
puts '.'
reopen_issues
puts '.'
end
Timecop.return
print '.'
end
private
def create_milestone
milestone_params = {
title: "Sprint - #{FFaker::Lorem.sentence}",
description: FFaker::Lorem.sentence,
state: 'active',
start_date: Date.today,
due_date: rand(5..10).days.from_now
}
@milestone = Milestones::CreateService.new(@project, @project.team.users.sample, milestone_params).execute
end
def create_issues
20.times do
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
state: 'opened',
milestone: @milestone,
assignee: @project.team.users.sample,
weight: rand(1..9)
}
Issues::CreateService.new(@project, @project.team.users.sample, issue_params).execute
end
end
def close_issues
@milestone.start_date.upto(@milestone.due_date) do |date|
Timecop.travel(date)
close_number = rand(0..2)
open_issues = @milestone.issues.opened
open_issues = open_issues.slice(0..close_number)
open_issues.each do |issue|
Issues::CloseService.new(@project, @project.team.users.sample, {}).execute(issue)
end
end
Timecop.return
end
def reopen_issues
count = @milestone.issues.closed.count / 3
issues = @milestone.issues.closed.slice(0..rand(count))
issues.each { |i| i.update(state: 'reopened') }
end
end
Gitlab::Seeder.quiet do
if project_id = ENV['PROJECT_ID']
project = Project.find(project_id)
seeder = Gitlab::Seeder::Burndown.new(project)
seeder.seed!
else
Project.all.each do |project|
seeder = Gitlab::Seeder::Burndown.new(project)
seeder.seed!
end
end
end
...@@ -101,7 +101,7 @@ Indexing is 65.55% complete (6555/10000 projects) ...@@ -101,7 +101,7 @@ Indexing is 65.55% complete (6555/10000 projects)
By default, one job is created for every 300 projects. For large numbers of By default, one job is created for every 300 projects. For large numbers of
projects, you may wish to increase the batch size, by setting the `BATCH` projects, you may wish to increase the batch size, by setting the `BATCH`
environment variable. You may also wish to consider [throttling](../administration/operations/sidekiq_job_throttling.md) environment variable. You may also wish to consider [throttling](../administration/operations/sidekiq_job_throttling.md)
the `elastic_batch_project_indexer` queue , as this step can be I/O-intensive. the `elastic_batch_project_indexer` queue, as this step can be I/O-intensive.
You can also run the initial indexing synchronously - this is most useful if You can also run the initial indexing synchronously - this is most useful if
you have a small number of projects, or need finer-grained control over indexing you have a small number of projects, or need finer-grained control over indexing
...@@ -121,16 +121,16 @@ If you want to run several tasks in parallel (probably in separate terminal ...@@ -121,16 +121,16 @@ If you want to run several tasks in parallel (probably in separate terminal
windows) you can provide the `ID_FROM` and `ID_TO` parameters: windows) you can provide the `ID_FROM` and `ID_TO` parameters:
``` ```
ID_FROM=1001 ID_TO=2000 sudo gitlab-rake gitlab:elastic:index_repositories sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000
``` ```
Where `ID_FROM` and `ID_TO` are project IDs. Both parameters are optional. Where `ID_FROM` and `ID_TO` are project IDs. Both parameters are optional.
As an example, if you have 3,000 repositories and you want to run three separate indexing tasks, you might run: As an example, if you have 3,000 repositories and you want to run three separate indexing tasks, you might run:
``` ```
ID_TO=1000 sudo gitlab-rake gitlab:elastic:index_repositories sudo gitlab-rake gitlab:elastic:index_repositories ID_TO=1000
ID_FROM=1001 ID_TO=2000 sudo gitlab-rake gitlab:elastic:index_repositories sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=1001 ID_TO=2000
ID_FROM=2001 sudo gitlab-rake gitlab:elastic:index_repositories sudo gitlab-rake gitlab:elastic:index_repositories ID_FROM=2001
``` ```
Sometimes your repository index process `gitlab:elastic:index_repositories` or Sometimes your repository index process `gitlab:elastic:index_repositories` or
...@@ -144,7 +144,7 @@ it will check every project repository again to make sure that every commit in ...@@ -144,7 +144,7 @@ it will check every project repository again to make sure that every commit in
that repository is indexed, it can be useful in case if your index is outdated: that repository is indexed, it can be useful in case if your index is outdated:
``` ```
UPDATE_INDEX=true ID_TO=1000 sudo gitlab-rake gitlab:elastic:index_repositories sudo gitlab-rake gitlab:elastic:index_repositories UPDATE_INDEX=true ID_TO=1000
``` ```
You can also use the `gitlab:elastic:clear_index_status` Rake task to force the You can also use the `gitlab:elastic:clear_index_status` Rake task to force the
......
...@@ -10,7 +10,15 @@ module Gitlab ...@@ -10,7 +10,15 @@ module Gitlab
end end
def labels def labels
metadata['labels'] metadata.fetch('labels', {})
end
def track
labels.fetch('track', 'stable')
end
def stable?
track == 'stable'
end end
def outdated? def outdated?
...@@ -52,7 +60,12 @@ module Gitlab ...@@ -52,7 +60,12 @@ module Gitlab
end end
def deployment_instance(n, name, status) def deployment_instance(n, name, status)
{ status: status, tooltip: "#{name} (pod #{n}) #{status.capitalize}" } {
status: status,
tooltip: "#{name} (pod #{n}) #{status.capitalize}",
track: track,
stable: stable?
}
end end
def metadata def metadata
......
...@@ -72,4 +72,39 @@ namespace :geo do ...@@ -72,4 +72,39 @@ namespace :geo do
Rails.application.config = @previous_config[:config] Rails.application.config = @previous_config[:config]
end end
end end
desc 'Make this node the Geo primary'
task :set_primary_node, [:ssh_key_filename] => :environment do |_, args|
filename = args[:ssh_key_filename]
abort 'GitLab Geo is not supported with this license. Please contact sales@gitlab.com.' unless Gitlab::Geo.license_allows?
abort 'You must specify a filename of an SSH public key' unless filename.present?
abort 'GitLab Geo primary node already present' if Gitlab::Geo.primary_node.present?
public_key = load_ssh_public_key(filename)
abort "Invalid SSH public key in #{filename}, aborting" unless public_key
set_primary_geo_node(public_key)
end
def load_ssh_public_key(filename)
File.open(filename).read
rescue => e
puts "Error opening #{filename}: #{e}".color(:red)
nil
end
def set_primary_geo_node(public_key)
params = { host: Gitlab.config.gitlab.host,
port: Gitlab.config.gitlab.port,
relative_url_root: Gitlab.config.gitlab.relative_url_root,
primary: true,
geo_node_key_attributes: { key: public_key } }
node = GeoNode.new(params)
puts "Saving primary GeoNode with URL #{node.url}".color(:green)
node.save
puts "Error saving GeoNode:\n#{node.errors.full_messages.join("\n")}".color(:red) unless node.persisted?
end
end end
...@@ -11,6 +11,8 @@ describe Projects::EnvironmentsController do ...@@ -11,6 +11,8 @@ describe Projects::EnvironmentsController do
end end
before do before do
allow_any_instance_of(License).to receive(:add_on?).and_return(false)
project.team << [user, :master] project.team << [user, :master]
sign_in(user) sign_in(user)
...@@ -27,6 +29,8 @@ describe Projects::EnvironmentsController do ...@@ -27,6 +29,8 @@ describe Projects::EnvironmentsController do
context 'when requesting JSON response for folders' do context 'when requesting JSON response for folders' do
before do before do
allow_any_instance_of(Environment).to receive(:deployment_service_ready?).and_return(true)
create(:environment, project: project, create(:environment, project: project,
name: 'staging/review-1', name: 'staging/review-1',
state: :available) state: :available)
...@@ -44,15 +48,19 @@ describe Projects::EnvironmentsController do ...@@ -44,15 +48,19 @@ describe Projects::EnvironmentsController do
context 'when requesting available environments scope' do context 'when requesting available environments scope' do
before do before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(true)
get :index, environment_params(format: :json, scope: :available) get :index, environment_params(format: :json, scope: :available)
end end
it 'responds with a payload describing available environments' do it 'responds with a payload describing available environments' do
expect(environments.count).to eq 2 expect(environments.count).to eq 2
expect(environments.first['name']).to eq 'production' expect(environments.first['name']).to eq 'production'
expect(environments.first['latest']['rollout_status_path']).to be_present
expect(environments.second['name']).to eq 'staging' expect(environments.second['name']).to eq 'staging'
expect(environments.second['size']).to eq 2 expect(environments.second['size']).to eq 2
expect(environments.second['latest']['name']).to eq 'staging/review-2' expect(environments.second['latest']['name']).to eq 'staging/review-2'
expect(environments.second['latest']['rollout_status_path']).to be_present
end end
it 'contains values describing environment scopes sizes' do it 'contains values describing environment scopes sizes' do
...@@ -78,6 +86,19 @@ describe Projects::EnvironmentsController do ...@@ -78,6 +86,19 @@ describe Projects::EnvironmentsController do
expect(json_response['stopped_count']).to eq 1 expect(json_response['stopped_count']).to eq 1
end end
end end
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(false)
get :index, environment_params(format: :json)
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
end
end
end end
end end
...@@ -233,6 +254,7 @@ describe Projects::EnvironmentsController do ...@@ -233,6 +254,7 @@ describe Projects::EnvironmentsController do
let(:project) { create(:kubernetes_project) } let(:project) { create(:kubernetes_project) }
before do before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(true)
allow_any_instance_of(Environment).to receive(:deployment_service_ready?).and_return(true) allow_any_instance_of(Environment).to receive(:deployment_service_ready?).and_return(true)
end end
...@@ -256,6 +278,18 @@ describe Projects::EnvironmentsController do ...@@ -256,6 +278,18 @@ describe Projects::EnvironmentsController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
end end
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(false)
end
it 'does not return any data' do
get :status, environment_params
expect(response).to have_http_status(:not_found)
end
end
end end
describe 'GET #metrics' do describe 'GET #metrics' do
......
[
{
"status": "finished",
"tooltip": "production (pod 0) Finished",
"track": "stable",
"stable": true
},
{
"status": "deploying",
"tooltip": "production (pod 1) Deploying",
"track": "stable",
"stable": true
},
{
"status": "failed",
"tooltip": "production (pod 2) Failed",
"track": "stable",
"stable": true
},
{
"status": "ready",
"tooltip": "production (pod 3) Ready",
"track": "stable",
"stable": true
},
{
"status": "preparing",
"tooltip": "production (pod 4) Preparing",
"track": "stable",
"stable": true
},
{
"status": "waiting",
"tooltip": "production (pod 5) Waiting",
"track": "stable",
"stable": true
},
{
"status": "finished",
"tooltip": "production-canary (pod 0) Finished",
"track": "canary",
"stable": false
},
{
"status": "deploying",
"tooltip": "production-canary (pod 1) Deploying",
"track": "canary",
"stable": false
},
{
"status": "failed",
"tooltip": "production-canary (pod 2) Failed",
"track": "canary",
"stable": false
},
{
"status": "ready",
"tooltip": "production-canary (pod 3) Ready",
"track": "canary",
"stable": false
},
{
"status": "preparing",
"tooltip": "production-canary (pod 4) Preparing",
"track": "canary",
"stable": false
},
{
"status": "waiting",
"tooltip": "production-canary (pod 5) Waiting",
"track": "canary",
"stable": false
}
]
...@@ -30,4 +30,15 @@ describe('Deploy Board Instance', () => { ...@@ -30,4 +30,15 @@ describe('Deploy Board Instance', () => {
expect(component.$el.classList.contains('deploy-board-instance-deploying')).toBe(true); expect(component.$el.classList.contains('deploy-board-instance-deploying')).toBe(true);
expect(component.$el.getAttribute('data-title')).toEqual(''); expect(component.$el.getAttribute('data-title')).toEqual('');
}); });
it('should render a div with canary class when stable prop is provided as false', () => {
const component = new DeployBoardInstanceComponent({
propsData: {
status: 'deploying',
stable: false,
},
}).$mount();
expect(component.$el.classList.contains('deploy-board-instance-canary')).toBe(true);
});
}); });
...@@ -5,60 +5,70 @@ describe Gitlab::Kubernetes::Deployment do ...@@ -5,60 +5,70 @@ describe Gitlab::Kubernetes::Deployment do
describe '#name' do describe '#name' do
let(:params) { named(:selected) } let(:params) { named(:selected) }
it { expect(deployment.name).to eq(:selected) } it { expect(deployment.name).to eq(:selected) }
end end
describe '#labels' do describe '#labels' do
let(:params) { make('metadata', 'labels' => :selected) } let(:params) { make('metadata', 'labels' => :selected) }
it { expect(deployment.labels).to eq(:selected) } it { expect(deployment.labels).to eq(:selected) }
end end
describe '#outdated?' do describe '#outdated?' do
context 'when outdated' do context 'when outdated' do
let(:params) { generation(2, 1) } let(:params) { generation(2, 1) }
it { expect(deployment.outdated?).to be_truthy } it { expect(deployment.outdated?).to be_truthy }
end end
context 'when up to date' do context 'when up to date' do
let(:params) { generation(2, 2) } let(:params) { generation(2, 2) }
it { expect(deployment.outdated?).to be_falsy } it { expect(deployment.outdated?).to be_falsy }
end end
context 'when ahead of latest' do context 'when ahead of latest' do
let(:params) { generation(1, 2) } let(:params) { generation(1, 2) }
it { expect(deployment.outdated?).to be_falsy } it { expect(deployment.outdated?).to be_falsy }
end end
end end
describe '#wanted_replicas' do describe '#wanted_replicas' do
let(:params) { make('spec', 'replicas' => :selected ) } let(:params) { make('spec', 'replicas' => :selected ) }
it { expect(deployment.wanted_replicas).to eq(:selected) } it { expect(deployment.wanted_replicas).to eq(:selected) }
end end
describe '#finished_replicas' do describe '#finished_replicas' do
let(:params) { make('status', 'availableReplicas' => :selected) } let(:params) { make('status', 'availableReplicas' => :selected) }
it { expect(deployment.finished_replicas).to eq(:selected) } it { expect(deployment.finished_replicas).to eq(:selected) }
end end
describe '#deploying_replicas' do describe '#deploying_replicas' do
let(:params) { make('status', 'availableReplicas' => 2, 'updatedReplicas' => 4) } let(:params) { make('status', 'availableReplicas' => 2, 'updatedReplicas' => 4) }
it { expect(deployment.deploying_replicas).to eq(2) } it { expect(deployment.deploying_replicas).to eq(2) }
end end
describe '#waiting_replicas' do describe '#waiting_replicas' do
let(:params) { combine(make('spec', 'replicas' => 4), make('status', 'updatedReplicas' => 2)) } let(:params) { combine(make('spec', 'replicas' => 4), make('status', 'updatedReplicas' => 2)) }
it { expect(deployment.waiting_replicas).to eq(2) } it { expect(deployment.waiting_replicas).to eq(2) }
end end
describe '#instances' do describe '#instances' do
context 'when unnamed' do context 'when unnamed' do
let(:params) { combine(generation(1, 1), instances) } let(:params) { combine(generation(1, 1), instances) }
it 'returns all instances as unknown and waiting' do it 'returns all instances as unknown and waiting' do
expected = [ expected = [
{ status: 'waiting', tooltip: 'unknown (pod 0) Waiting' }, { status: 'waiting', tooltip: 'unknown (pod 0) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'unknown (pod 1) Waiting' }, { status: 'waiting', tooltip: 'unknown (pod 1) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'unknown (pod 2) Waiting' }, { status: 'waiting', tooltip: 'unknown (pod 2) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'unknown (pod 3) Waiting' }, { status: 'waiting', tooltip: 'unknown (pod 3) Waiting', track: 'stable', stable: true },
] ]
expect(deployment.instances).to eq(expected) expect(deployment.instances).to eq(expected)
...@@ -67,12 +77,13 @@ describe Gitlab::Kubernetes::Deployment do ...@@ -67,12 +77,13 @@ describe Gitlab::Kubernetes::Deployment do
context 'when outdated' do context 'when outdated' do
let(:params) { combine(named('foo'), generation(1, 0), instances) } let(:params) { combine(named('foo'), generation(1, 0), instances) }
it 'returns all instances as named and waiting' do it 'returns all instances as named and waiting' do
expected = [ expected = [
{ status: 'waiting', tooltip: 'foo (pod 0) Waiting' }, { status: 'waiting', tooltip: 'foo (pod 0) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 1) Waiting' }, { status: 'waiting', tooltip: 'foo (pod 1) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 2) Waiting' }, { status: 'waiting', tooltip: 'foo (pod 2) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 3) Waiting' }, { status: 'waiting', tooltip: 'foo (pod 3) Waiting', track: 'stable', stable: true },
] ]
expect(deployment.instances).to eq(expected) expect(deployment.instances).to eq(expected)
...@@ -84,15 +95,44 @@ describe Gitlab::Kubernetes::Deployment do ...@@ -84,15 +95,44 @@ describe Gitlab::Kubernetes::Deployment do
it 'returns all instances' do it 'returns all instances' do
expected = [ expected = [
{ status: 'finished', tooltip: 'foo (pod 0) Finished' }, { status: 'finished', tooltip: 'foo (pod 0) Finished', track: 'stable', stable: true },
{ status: 'deploying', tooltip: 'foo (pod 1) Deploying' }, { status: 'deploying', tooltip: 'foo (pod 1) Deploying', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 2) Waiting' }, { status: 'waiting', tooltip: 'foo (pod 2) Waiting', track: 'stable', stable: true },
{ status: 'waiting', tooltip: 'foo (pod 3) Waiting' }, { status: 'waiting', tooltip: 'foo (pod 3) Waiting', track: 'stable', stable: true },
] ]
expect(deployment.instances).to eq(expected) expect(deployment.instances).to eq(expected)
end end
end end
context 'with track label' do
let(:labels) { { 'track' => track } }
let(:params) { combine(named('foo', labels), generation(1, 0), instances(1, 1, 1, labels)) }
context 'when marked as stable' do
let(:track) { 'stable' }
it 'returns all instances' do
expected = [
{ status: 'waiting', tooltip: 'foo (pod 0) Waiting', track: 'stable', stable: true },
]
expect(deployment.instances).to eq(expected)
end
end
context 'when marked as canary' do
let(:track) { 'canary' }
it 'returns all instances' do
expected = [
{ status: 'waiting', tooltip: 'foo (pod 0) Waiting', track: 'canary', stable: false },
]
expect(deployment.instances).to eq(expected)
end
end
end
end end
def generation(expected, observed) def generation(expected, observed)
...@@ -102,14 +142,14 @@ describe Gitlab::Kubernetes::Deployment do ...@@ -102,14 +142,14 @@ describe Gitlab::Kubernetes::Deployment do
) )
end end
def named(name = "foo") def named(name = "foo", labels = {})
make('metadata', 'name' => name) make('metadata', 'name' => name, 'labels' => labels)
end end
def instances def instances(replicas = 4, available = 1, updated = 2, labels = {})
combine( combine(
make('spec', 'replicas' => 4), make('spec', 'replicas' => replicas),
make('status', 'availableReplicas' => 1, 'updatedReplicas' => 2), make('status', 'availableReplicas' => available, 'updatedReplicas' => updated),
) )
end end
......
...@@ -2,16 +2,25 @@ require 'spec_helper' ...@@ -2,16 +2,25 @@ require 'spec_helper'
describe Gitlab::Kubernetes::RolloutStatus do describe Gitlab::Kubernetes::RolloutStatus do
include KubernetesHelpers include KubernetesHelpers
let(:specs_all_finished) { [kube_deployment(name: 'one'), kube_deployment(name: 'two')] }
let(:specs_half_finished) do let(:track) { nil }
let(:specs) { specs_all_finished }
let(:specs_none) { [] }
let(:specs_all_finished) do
[ [
kube_deployment(name: 'one'), kube_deployment(name: 'one'),
kube_deployment(name: 'two').deep_merge('status' => { 'availableReplicas' => 0 }) kube_deployment(name: 'two', track: track)
] ]
end end
let(:specs) { specs_all_finished } let(:specs_half_finished) do
let(:specs_none) { [] } [
kube_deployment(name: 'one'),
kube_deployment(name: 'two', track: track)
.deep_merge('status' => { 'availableReplicas' => 0 })
]
end
subject(:rollout_status) { described_class.from_specs(*specs) } subject(:rollout_status) { described_class.from_specs(*specs) }
...@@ -24,20 +33,39 @@ describe Gitlab::Kubernetes::RolloutStatus do ...@@ -24,20 +33,39 @@ describe Gitlab::Kubernetes::RolloutStatus do
end end
describe '#instances' do describe '#instances' do
context 'for stable track' do
it 'stores the union of deployment instances' do it 'stores the union of deployment instances' do
expected = [ expected = [
{ status: 'finished', tooltip: 'one (pod 0) Finished' }, { status: 'finished', tooltip: 'one (pod 0) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 1) Finished' }, { status: 'finished', tooltip: 'one (pod 1) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 2) Finished' }, { status: 'finished', tooltip: 'one (pod 2) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'two (pod 0) Finished' }, { status: 'finished', tooltip: 'two (pod 0) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'two (pod 1) Finished' }, { status: 'finished', tooltip: 'two (pod 1) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'two (pod 2) Finished' }, { status: 'finished', tooltip: 'two (pod 2) Finished', track: 'stable', stable: true },
] ]
expect(rollout_status.instances).to eq(expected) expect(rollout_status.instances).to eq(expected)
end end
end end
context 'for stable track' do
let(:track) { 'canary' }
it 'stores the union of deployment instances' do
expected = [
{ status: 'finished', tooltip: 'one (pod 0) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 1) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'one (pod 2) Finished', track: 'stable', stable: true },
{ status: 'finished', tooltip: 'two (pod 0) Finished', track: 'canary', stable: false },
{ status: 'finished', tooltip: 'two (pod 1) Finished', track: 'canary', stable: false },
{ status: 'finished', tooltip: 'two (pod 2) Finished', track: 'canary', stable: false },
]
expect(rollout_status.instances).to eq(expected)
end
end
end
describe '#completion' do describe '#completion' do
subject { rollout_status.completion } subject { rollout_status.completion }
...@@ -47,6 +75,7 @@ describe Gitlab::Kubernetes::RolloutStatus do ...@@ -47,6 +75,7 @@ describe Gitlab::Kubernetes::RolloutStatus do
context 'when half of the instances are finished' do context 'when half of the instances are finished' do
let(:specs) { specs_half_finished } let(:specs) { specs_half_finished }
it { is_expected.to eq(50) } it { is_expected.to eq(50) }
end end
end end
...@@ -60,6 +89,7 @@ describe Gitlab::Kubernetes::RolloutStatus do ...@@ -60,6 +89,7 @@ describe Gitlab::Kubernetes::RolloutStatus do
context 'when half of the instances are finished' do context 'when half of the instances are finished' do
let(:specs) { specs_half_finished } let(:specs) { specs_half_finished }
it { is_expected.to be_falsy} it { is_expected.to be_falsy}
end end
end end
......
require 'spec_helper'
describe Burndown, models: true do
let(:start_date) { "2017-03-01" }
let(:due_date) { "2017-03-05" }
let(:milestone) { create(:milestone, start_date: start_date, due_date: due_date) }
let(:project) { milestone.project }
let(:user) { create(:user) }
let(:issue_params) do
{
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
state: 'opened',
milestone: milestone,
weight: 2,
project_id: project.id
}
end
before do
project.add_master(user)
build_sample
end
after do
Timecop.return
end
subject { described_class.new(milestone).to_json }
it "generates an array with date, issue count and weight" do
expect(subject).to eq([
["2017-03-01", 33, 66],
["2017-03-02", 35, 70],
["2017-03-03", 28, 56],
["2017-03-04", 32, 64],
["2017-03-05", 21, 42]
].to_json)
end
it "returns empty array if milestone start date is nil" do
milestone.update(start_date: nil)
expect(subject).to eq([].to_json)
end
it "returns empty array if milestone due date is nil" do
milestone.update(due_date: nil)
expect(subject).to eq([].to_json)
end
it "it counts until today if milestone due date > Date.today" do
Timecop.travel(milestone.due_date - 1.day)
expect(JSON.parse(subject).last[0]).to eq(Time.now.strftime("%Y-%m-%d"))
end
# Creates, closes and reopens issues only for odd days numbers
def build_sample
milestone.start_date.upto(milestone.due_date) do |date|
day = date.day
next if day.even?
count = day * 4
Timecop.travel(date)
# Create issues
issues = create_list(:issue, count, issue_params)
# Close issues
closed = issues.slice(0..count / 2)
closed.each(&:close)
# Reopen issues
closed.slice(0..count / 4).each(&:reopen)
end
Timecop.travel(due_date)
end
end
...@@ -292,7 +292,7 @@ describe 'Git HTTP requests', lib: true do ...@@ -292,7 +292,7 @@ describe 'Git HTTP requests', lib: true do
it 'responds with status 403' do it 'responds with status 403' do
msg = 'No GitLab Enterprise Edition license has been provided yet. Pushing code and creation of issues and merge requests has been disabled. Ask an admin to upload a license to activate this functionality.' msg = 'No GitLab Enterprise Edition license has been provided yet. Pushing code and creation of issues and merge requests has been disabled. Ask an admin to upload a license to activate this functionality.'
allow(License).to receive(:current).and_return(false) allow(License).to receive(:current).and_return(nil)
upload(path, env) do |response| upload(path, env) do |response|
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
......
require 'spec_helper' require 'spec_helper'
describe EnvironmentEntity do describe EnvironmentEntity do
let(:user) { create(:user) }
let(:environment) { create(:environment) }
let(:entity) do let(:entity) do
described_class.new(environment, request: double(user: nil)) described_class.new(environment, request: double(user: user))
end end
let(:environment) { create(:environment) }
subject { entity.as_json } subject { entity.as_json }
before do
allow_any_instance_of(License).to receive(:add_on?).and_return(false)
environment.project.team << [user, :master]
end
it 'exposes latest deployment' do it 'exposes latest deployment' do
expect(subject).to include(:last_deployment) expect(subject).to include(:last_deployment)
end end
...@@ -38,6 +46,7 @@ describe EnvironmentEntity do ...@@ -38,6 +46,7 @@ describe EnvironmentEntity do
context 'with deployment service ready' do context 'with deployment service ready' do
before do before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(true)
allow(environment).to receive(:deployment_service_ready?).and_return(true) allow(environment).to receive(:deployment_service_ready?).and_return(true)
end end
...@@ -47,4 +56,15 @@ describe EnvironmentEntity do ...@@ -47,4 +56,15 @@ describe EnvironmentEntity do
expect(subject[:rollout_status_path]).to eq(expected) expect(subject[:rollout_status_path]).to eq(expected)
end end
end end
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(false)
allow(environment).to receive(:deployment_service_ready?).and_return(true)
end
it 'does not expose rollout_status_path' do
expect(subject[:rollout_status_path]).to be_blank
end
end
end end
...@@ -8,6 +8,19 @@ describe Issues::BuildService, services: true do ...@@ -8,6 +8,19 @@ describe Issues::BuildService, services: true do
project.team << [user, :developer] project.team << [user, :developer]
end end
context 'with an issue template' do
describe '#execute' do
it 'fills in the template in the description' do
project = build(:project, issues_template: 'Work hard, play hard!')
service = described_class.new(project, user)
issue = service.execute
expect(issue.description).to eq('Work hard, play hard!')
end
end
end
context 'for a single discussion' do context 'for a single discussion' do
describe '#execute' do describe '#execute' do
let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) } let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) }
...@@ -25,6 +38,16 @@ describe Issues::BuildService, services: true do ...@@ -25,6 +38,16 @@ describe Issues::BuildService, services: true do
expect(issue.description).to include('Almost done') expect(issue.description).to include('Almost done')
end end
context 'with an issue template' do
let(:project) { create(:project, :repository, issues_template: 'Work hard, play hard!') }
it 'picks the discussion description over the issue template' do
issue = service.execute
expect(issue.description).to include('Almost done')
end
end
end end
end end
......
...@@ -85,12 +85,15 @@ module KubernetesHelpers ...@@ -85,12 +85,15 @@ module KubernetesHelpers
} }
end end
def kube_deployment(name: "kube-deployment", app: "valid-deployment-label") def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
{ {
"metadata" => { "metadata" => {
"name" => name, "name" => name,
"generation" => 4, "generation" => 4,
"labels" => { "app" => app }, "labels" => {
"app" => app,
"track" => track
}.compact,
}, },
"spec" => { "replicas" => 3 }, "spec" => { "replicas" => 3 },
"status" => { "status" => {
......
require 'rake_helper'
describe 'geo rake tasks' do
before do
Rake.application.rake_require 'tasks/geo'
end
describe 'set_primary_node task' do
let(:ssh_key) { 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUkxk8m9rVYZ1q4/5xpg3TwTM9QFw3TinPFkyWsiACFKjor3byV6g3vHWTuIS70E7wk2JTXGL0wdrfUG6iQDJuP0BYNxjkluB14nIAfPuXN7V73QY/cqvHogw5o6pPRFD+Szke6FzouNQ70Z/qrM1k7me3e9DMuscMMrMTOR2HLKppNQyP4Jp0WJOyncdWB2NxKXTezy/ZnHv+BdhC0q0JW3huIx9qkBCHio7x8BdyJLMF9KxNYIuCkbP3exs5wgb+qGrjSri6LfAVq8dJ2VYibWxdsUG6iITJF+G4qbcyQjgiMLbxCfNd9bjwmkxSGvFn2EPsAFKzxyAvYFWb/y91 test@host' }
before do
expect(Gitlab::Geo).to receive(:license_allows?).and_return(true)
end
it 'creates a GeoNode' do
begin
file = Tempfile.new('geo-test-')
file.write(ssh_key)
path = file.path
file.close
expect(GeoNode.count).to eq(0)
run_rake_task('geo:set_primary_node', path)
expect(GeoNode.count).to eq(1)
node = GeoNode.first
expect(node.primary).to be_truthy
expect(node.geo_node_key.key).to eq(ssh_key)
ensure
file.unlink
end
end
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