Commit c5dcbff6 authored by Felipe Artur's avatar Felipe Artur

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee

parents 792e4ddb 0f73163e
......@@ -55,7 +55,15 @@ export default Vue.component('pipelines-table', {
},
shouldRenderEmptyState() {
return !this.state.pipelines.length && !this.isLoading;
return !this.state.pipelines.length &&
!this.isLoading &&
!this.hasError;
},
shouldRenderTable() {
return !this.isLoading &&
this.state.pipelines.length > 0 &&
!this.hasError;
},
},
......@@ -145,8 +153,12 @@ export default Vue.component('pipelines-table', {
template: `
<div class="content-list pipelines">
<div class="realtime-loading" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
<div
class="realtime-loading"
v-if="isLoading">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</div>
<empty-state
......@@ -155,8 +167,9 @@ export default Vue.component('pipelines-table', {
<error-state v-if="shouldRenderErrorState" />
<div class="table-holder"
v-if="!isLoading && state.pipelines.length > 0">
<div
class="table-holder"
v-if="shouldRenderTable">
<pipelines-table-component
:pipelines="state.pipelines"
:service="service" />
......
......@@ -2,7 +2,7 @@
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table';
import EnvironmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
......
<script>
/* global Flash */
/* eslint-disable no-new */
......@@ -15,7 +16,6 @@ export default {
service: {
type: Object,
required: true,
default: () => ({}),
},
},
......@@ -57,9 +57,12 @@ export default {
return !action.playable;
},
},
template: `
<div class="btn-group" role="group">
};
</script>
<template>
<div
class="btn-group"
role="group">
<button
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
......@@ -87,9 +90,9 @@ export default {
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)"
:class="{ 'disabled': isActionDisabled(action) }"
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
${playIconSvg}
<span v-html="playIconSvg"></span>
<span>
{{action.name}}
</span>
......@@ -97,5 +100,4 @@ export default {
</li>
</ul>
</div>
`,
};
</template>
/**
* Environment Item Component
*
* Renders a table row for each environment.
*/
<script>
import Timeago from 'timeago.js';
import '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
import RollbackComponent from './environment_rollback';
......@@ -441,33 +436,36 @@ export default {
eventHub.$emit('toggleFolder', this.model, this.folderUrl);
},
},
template: `
};
</script>
<template>
<tr :class="{ 'js-child-row': model.isChildren }">
<td>
<span class="deploy-board-icon"
<span
class="deploy-board-icon"
v-if="model.hasDeployBoard"
@click="toggleDeployBoard(model)">
<i v-show="!model.isDeployBoardVisible"
<i
v-show="!model.isDeployBoardVisible"
class="fa fa-caret-right"
aria-hidden="true" />
<i v-show="model.isDeployBoardVisible"
<i
v-show="model.isDeployBoardVisible"
class="fa fa-caret-down"
aria-hidden="true" />
</span>
<a v-if="!model.isFolder"
<a
v-if="!model.isFolder"
class="environment-name"
:class="{ 'prepend-left-default': model.isChildren }"
:href="environmentPath">
{{model.name}}
</a>
<span v-if="model.isFolder"
<span
v-else
class="folder-name"
@click="onClickFolder"
role="button">
......@@ -484,7 +482,9 @@ export default {
</span>
<span class="folder-icon">
<i class="fa fa-folder" aria-hidden="true"></i>
<i
class="fa fa-folder"
aria-hidden="true" />
</span>
<span>
......@@ -504,8 +504,11 @@ export default {
<span v-if="!model.isFolder && deploymentHasUser">
by
<a :href="deploymentUser.web_url" class="js-deploy-user-container">
<img class="avatar has-tooltip s20"
<a
:href="deploymentUser.web_url"
class="js-deploy-user-container">
<img
class="avatar has-tooltip s20"
:src="deploymentUser.avatar_url"
:alt="userImageAltDescription"
:title="deploymentUser.username" />
......@@ -514,7 +517,8 @@ export default {
</td>
<td class="environments-build-cell">
<a v-if="shouldRenderBuildName"
<a
v-if="shouldRenderBuildName"
class="build-link"
:href="buildPath">
{{buildName}}
......@@ -522,7 +526,9 @@ export default {
</td>
<td>
<div v-if="!model.isFolder && hasLastDeploymentKey" class="js-commit-component">
<div
v-if="!model.isFolder && hasLastDeploymentKey"
class="js-commit-component">
<commit-component
:tag="commitTag"
:commit-ref="commitRef"
......@@ -531,43 +537,55 @@ export default {
:title="commitTitle"
:author="commitAuthor"/>
</div>
<p v-if="!model.isFolder && !hasLastDeploymentKey" class="commit-title">
<p
v-if="!model.isFolder && !hasLastDeploymentKey"
class="commit-title">
No deployments yet
</p>
</td>
<td>
<span v-if="!model.isFolder && canShowDate"
<span
v-if="!model.isFolder && canShowDate"
class="environment-created-date-timeago">
{{createdDate}}
</span>
</td>
<td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
<div
v-if="!model.isFolder"
class="btn-group pull-right"
role="group">
<actions-component
v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
<external-url-component
v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<monitoring-button-component v-if="monitoringUrl && canReadEnvironment"
<monitoring-button-component
v-if="monitoringUrl && canReadEnvironment"
:monitoring-url="monitoringUrl"/>
<terminal-button-component v-if="model && model.terminal_path"
<terminal-button-component
v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
<stop-component
v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"
:service="service"/>
<rollback-component v-if="canRetry && canCreateDeployment"
<rollback-component
v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
:service="service"/>
</div>
</td>
</tr>
`,
};
</template>
<script>
/**
* Render environments table.
*
* Dumb component used to render top level environments and
* the folder view.
*/
import EnvironmentTableRowComponent from './environment_item';
import EnvironmentTableRowComponent from './environment_item.vue';
import DeployBoard from './deploy_board_component';
export default {
......@@ -32,6 +30,17 @@ export default {
default: false,
},
service: {
type: Object,
required: true,
},
isLoadingFolderContent: {
type: Boolean,
required: false,
default: false,
},
toggleDeployBoard: {
type: Function,
required: false,
......@@ -43,18 +52,6 @@ export default {
required: false,
default: () => ({}),
},
service: {
type: Object,
required: true,
default: () => ({}),
},
isLoadingFolderContent: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
......@@ -62,29 +59,42 @@ export default {
return `${window.location.pathname}/folders/${model.folderName}`;
},
},
template: `
};
</script>
<template>
<table class="table ci-table">
<thead>
<tr>
<th class="environments-name">Environment</th>
<th class="environments-deploy">Last deployment</th>
<th class="environments-build">Job</th>
<th class="environments-commit">Commit</th>
<th class="environments-date">Updated</th>
<th class="environments-name">
Environment
</th>
<th class="environments-deploy">
Last deployment
</th>
<th class="environments-build">
Job
</th>
<th class="environments-commit">
Commit
</th>
<th class="environments-date">
Updated
</th>
<th class="environments-actions"></th>
</tr>
</thead>
<tbody>
<template v-for="model in environments"
<template
v-for="model in environments"
v-bind:model="model">
<tr is="environment-item"
<tr
is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service"
:toggleDeployBoard="toggleDeployBoard"
:service="service"></tr>
/>
<tr v-if="model.hasDeployBoard && model.isDeployBoardVisible" class="js-deploy-board-row">
<td colspan="6" class="deploy-board-container">
......@@ -93,29 +103,36 @@ export default {
:service="service"
:environmentID="model.id"
:deployBoardData="model.deployBoardData"
:endpoint="model.rollout_status_path">
</deploy-board>
:endpoint="model.rollout_status_path"
/>
</td>
</tr>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<tr v-if="isLoadingFolderContent">
<td colspan="6" class="text-center">
<i class="fa fa-spin fa-spinner fa-2x" aria-hidden="true"/>
<i
class="fa fa-spin fa-spinner fa-2x"
aria-hidden="true" />
</td>
</tr>
<template v-else>
<tr is="environment-item"
<tr
is="environment-item"
v-for="children in model.children"
:model="children"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:service="service"></tr>
:service="service" />
<tr>
<td colspan="6" class="text-center">
<a :href="folderUrl(model)" class="btn btn-default">
<td
colspan="6"
class="text-center">
<a
:href="folderUrl(model)"
class="btn btn-default">
Show all
</a>
</td>
......@@ -125,5 +142,4 @@ export default {
</template>
</tbody>
</table>
`,
};
</template>
......@@ -2,7 +2,7 @@
/* global Flash */
import Vue from 'vue';
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table';
import EnvironmentTable from '../components/environments_table.vue';
import EnvironmentsStore from '../stores/environments_store';
import TablePaginationComponent from '../../vue_shared/components/table_pagination';
import '../../lib/utils/common_utils';
......
......@@ -13,7 +13,7 @@ export default {
</script>
<template>
<div class="row empty-state">
<div class="row empty-state js-empty-state">
<div class="col-xs-12">
<div class="svg-content" v-html="pipelinesEmptyStateSVG" />
</div>
......
......@@ -18,11 +18,6 @@ export default {
required: false,
default: null,
},
isInstanceAdmin: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
......@@ -40,7 +35,6 @@ export default {
ref="enabled-checkbox"
type="checkbox"
id="service-desk-enabled-checkbox"
:disabled="!isInstanceAdmin"
:checked="isEnabled"
@change="onCheckboxToggle($event)">
<span class="descr">
......@@ -48,12 +42,6 @@ export default {
</span>
</label>
</div>
<p
ref="only-instance-admin-activate-message"
v-if="!isInstanceAdmin"
class="settings-message">
Only instance admins can activate/deactivate Service Desk
</p>
<template v-if="isEnabled">
<div
class="panel-slim panel-default">
......
......@@ -12,13 +12,10 @@ class ServiceDeskRoot {
this.wrapperElement.dataset.enabled !== 'false';
const incomingEmail = this.wrapperElement.dataset.incomingEmail;
const endpoint = this.wrapperElement.dataset.endpoint;
const isInstanceAdmin = typeof this.wrapperElement.dataset.isInstanceAdmin !== 'undefined' &&
this.wrapperElement.dataset.isInstanceAdmin !== 'false';
this.store = new ServiceDeskStore({
isEnabled,
incomingEmail,
isInstanceAdmin,
});
this.service = new ServiceDeskService(endpoint);
}
......@@ -51,8 +48,7 @@ class ServiceDeskRoot {
<service-desk-setting
:isEnabled="isEnabled"
:incomingEmail="incomingEmail"
:fetchError="fetchError"
:isInstanceAdmin="isInstanceAdmin" />
:fetchError="fetchError" />
`,
components: {
'service-desk-setting': ServiceDeskSetting,
......
......@@ -4,7 +4,6 @@ class ServiceDeskStore {
isEnabled: false,
incomingEmail: '',
fetchError: null,
isInstanceAdmin: false,
}, initialState);
}
......@@ -19,10 +18,6 @@ class ServiceDeskStore {
setFetchError(value) {
this.state.fetchError = value;
}
setIsInstanceAdmin(value) {
this.state.isInstanceAdmin = value;
}
}
export default ServiceDeskStore;
......@@ -247,6 +247,11 @@
}
}
.burndown-docs-link {
color: inherit;
text-decoration: underline;
}
.burndown-header {
margin: 24px 0 12px;
......
class Projects::ServiceDeskController < Projects::ApplicationController
before_action :authorize_admin_instance!, only: :update
before_action :authorize_admin_project!, only: :show
before_action :authorize_admin_project!
def show
json_response
......@@ -22,8 +21,4 @@ class Projects::ServiceDeskController < Projects::ApplicationController
format.json { render json: service_desk_attributes }
end
end
def authorize_admin_instance!
return render_404 unless current_user.admin?
end
end
......@@ -115,4 +115,21 @@ module MilestonesHelper
end
end
end
def data_warning_for(burndown)
return unless burndown
message =
if burndown.empty?
"The burndown chart can’t be shown, as all issues assigned to this milestone were closed on an older GitLab version before data was recorded. "
elsif !burndown.accurate?
"Some issues can’t be shown in the burndown chart, as they were closed on an older GitLab version before data was recorded. "
end
if message
message += link_to "About burndown charts", help_page_path('user/project/milestones/index', anchor: 'burndown-charts'), class: 'burndown-docs-link'
content_tag(:div, message.html_safe, id: "data-warning", class: "settings-message prepend-top-20")
end
end
end
class Burndown
attr_accessor :start_date, :due_date, :end_date, :issues_count, :issues_weight
attr_reader :start_date, :due_date, :end_date, :issues_count, :issues_weight, :accurate, :legacy_data
alias_method :accurate?, :accurate
alias_method :empty?, :legacy_data
def initialize(milestone)
@milestone = milestone
......@@ -8,6 +10,9 @@ class Burndown
@end_date = @milestone.due_date
@end_date = Date.today if @end_date.present? && @end_date > Date.today
@accurate = milestone_closed_issues.all?(&:closed_at)
@legacy_data = milestone_closed_issues.any? && milestone_closed_issues.none?(&:closed_at)
@issues_count, @issues_weight = milestone.issues.reorder(nil).pluck('COUNT(*), COALESCE(SUM(weight), 0)').first
end
......@@ -51,16 +56,20 @@ class Burndown
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 }
closed =
milestone_closed_issues.select do |issue|
(issue.closed_at&.to_date || start_date) == current_date
end
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')
def milestone_closed_issues
@milestone_closed_issues ||=
@milestone.issues.select("closed_at, weight, state").
where("state IN ('reopened', 'closed')").
order("closed_at ASC")
end
end
......@@ -14,8 +14,6 @@ module EE
delegate :actual_shared_runners_minutes_limit,
:shared_runners_minutes_used?, to: :namespace
before_validation :auto_refresh_service_desk_key
end
def shared_runners_available?
......@@ -29,29 +27,18 @@ module EE
def service_desk_address
return nil unless service_desk_available?
refresh_service_desk_key! if service_desk_mail_key.blank?
from = "service_desk+#{service_desk_mail_key}"
::Gitlab::IncomingEmail.reply_address(from)
end
def refresh_service_desk_key!
return unless service_desk_available?
config = ::Gitlab.config.incoming_email
wildcard = ::Gitlab::IncomingEmail::WILDCARD_PLACEHOLDER
self.service_desk_mail_key = SentNotification.reply_key
config.address&.gsub(wildcard, full_path)
end
private
def service_desk_available?
@service_desk_available ||=
EE::Gitlab::ServiceDesk.enabled? && service_desk_enabled?
end
return @service_desk_available if defined?(@service_desk_available)
def auto_refresh_service_desk_key
if service_desk_mail_key.blank? || service_desk_enabled_changed?
refresh_service_desk_key!
end
@service_desk_available = EE::Gitlab::ServiceDesk.enabled? && service_desk_enabled?
end
end
end
......@@ -134,8 +134,7 @@
= link_to icon('question-circle'), help_page_path('user/project/service_desk')
.js-service-desk-setting-root{ data: { endpoint: namespace_project_service_desk_path(@project.namespace, @project),
enabled: @project.service_desk_enabled,
incoming_email: (@project.service_desk_address if @project.service_desk_enabled),
is_instance_admin: current_user.admin? } }
incoming_email: (@project.service_desk_address if @project.service_desk_enabled) } }
%hr
%fieldset.features.append-bottom-default
......
- milestone = local_assigns[:milestone]
- project = local_assigns[:project]
- burndown = local_assigns[:burndown]
- can_generate_chart = burndown&.valid?
- can_generate_chart = burndown&.valid? && !burndown&.empty?
- warning = data_warning_for(burndown)
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('burndown_chart')
= warning
- if can_generate_chart
.burndown-header
%h3
......@@ -18,7 +21,7 @@
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?
- elsif can?(current_user, :admin_milestone, @project) && cookies['hide_burndown_message'].nil? && warning.nil?
.burndown-hint.content-block.container-fluid
= icon("times", class: "dismiss-icon")
.row
......
---
title: Add index to approvals.merge_request_id
merge_request:
author:
---
title: Add warning when burndown data is not accurate
merge_request:
author:
---
title: Check if incoming emails and email key are available for service desk
merge_request:
author:
class RemoveServiceDeskMailKeyFromProjects < ActiveRecord::Migration
DOWNTIME = false
def change
remove_column :projects, :service_desk_mail_key, :string
end
end
class AddIndexToApprovalsMergeRequestId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :approvals, :merge_request_id
end
def down
remove_concurrent_index :approvals, :merge_request_id
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170419065104) do
ActiveRecord::Schema.define(version: 20170421113144) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -141,6 +141,8 @@ ActiveRecord::Schema.define(version: 20170419065104) do
t.datetime "updated_at"
end
add_index "approvals", ["merge_request_id"], name: "index_approvals_on_merge_request_id", using: :btree
create_table "approver_groups", force: :cascade do |t|
t.integer "target_id", null: false
t.string "target_type", null: false
......@@ -1109,7 +1111,6 @@ ActiveRecord::Schema.define(version: 20170419065104) do
t.boolean "printing_merge_request_link_enabled", default: true, null: false
t.string "import_jid"
t.boolean "service_desk_enabled"
t.string "service_desk_mail_key"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......@@ -1125,7 +1126,6 @@ ActiveRecord::Schema.define(version: 20170419065104) do
add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
add_index "projects", ["service_desk_mail_key"], name: "index_projects_on_service_desk_mail_key", unique: true, using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["sync_time"], name: "index_projects_on_sync_time", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
......
......@@ -15,6 +15,7 @@ All technical content published by GitLab lives in the documentation, including:
- [API](api/README.md) Automate GitLab via a simple and powerful API.
- [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples.
- [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry.
- [Discussions](user/discussions/index.md) Threads, comments, and resolvable discussions in issues, commits, and merge requests.
- [Git Attributes](user/project/git_attributes.md) Managing Git attributes using a `.gitattributes` file.
- [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf) Download a PDF describing the most used Git operations.
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
......
# Analytics
- [Contribution Analytics](contribution_analytics.md)
- (EE) [Burndown charts](../user/project/milestones/index.md#burndown-charts)
......@@ -13,7 +13,7 @@ Create issues, labels, milestones, cast your vote, and review issues.
- [Create a new issue](../gitlab-basics/create-issue.md)
- [Assign labels to issues](../user/project/labels.md)
- [Use milestones as an overview of your project's tracker](../workflow/milestones.md)
- [Use milestones as an overview of your project's tracker](../user/project/milestones/index.md)
- [Use voting to express your like/dislike to issues and merge requests](../workflow/award_emoji.md)
## Collaborate
......
......@@ -333,7 +333,7 @@ A [platform](https://www.meteor.com) for building javascript apps.
### Milestones
Allow you to [organize issues](https://docs.gitlab.com/ce/workflow/milestones.html) and merge requests in GitLab into a cohesive group, optionally setting a due date. A common use is keeping track of an upcoming software version. Milestones are created per-project.
Allow you to [organize issues](https://docs.gitlab.com/ce/user/project/milestones/) and merge requests in GitLab into a cohesive group, optionally setting a due date. A common use is keeping track of an upcoming software version. Milestones are created per-project.
### Mirror Repositories
......
# Discussions
The ability to contribute conversationally is offered throughout GitLab.
You can leave a comment in the following places:
- issues
- merge requests
- snippets
- commits
- commit diffs
The comment area supports [Markdown] and [slash commands]. One can edit their
own comment at any time, and anyone with [Master access level][permissions] or
higher can also a comment made by someone else.
Apart from the standard comments, you also have the option to create a comment
in the form of a resolvable or threaded discussion.
## Resolvable discussions
>**Notes:**
- The main feature was [introduced][ce-5022] in GitLab 8.11.
- Resolvable discussions can be added only to merge request diffs.
Discussion resolution helps keep track of progress during planning or code review.
Resolving comments prevents you from forgetting to address feedback and lets you
hide discussions that are no longer relevant.
!["A discussion between two people on a piece of code"][discussion-view]
Comments and discussions can be resolved by anyone with at least Developer
access to the project or the author of the merge request.
### Jumping between unresolved discussions
When a merge request has a large number of comments it can be difficult to track
what remains unresolved. You can jump between unresolved discussions with the
Jump button next to the Reply field on a discussion.
You can also jump to the first unresolved discussion from the button next to the
resolved discussions tracker.
!["3/4 discussions resolved"][discussions-resolved]
### Marking a comment or discussion as resolved
You can mark a discussion as resolved by clicking the **Resolve discussion**
button at the bottom of the discussion.
!["Resolve discussion" button][resolve-discussion-button]
Alternatively, you can mark each comment as resolved individually.
!["Resolve comment" button][resolve-comment-button]
### Move all unresolved discussions in a merge request to an issue
> [Introduced][ce-8266] in GitLab 9.1
To continue all open discussions from a merge request in a new issue, click the
**Resolve all discussions in new issue** button.
![Open new issue for all unresolved discussions](img/btn_new_issue_for_all_discussions.png)
Alternatively, when your project only accepts merge requests [when all discussions
are resolved](#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved),
there will be an **open an issue to resolve them later** link in the merge
request widget.
![Link in merge request widget](img/resolve_discussion_open_issue.png)
This will prepare an issue with its content referring to the merge request and
the unresolved discussions.
![Issue mentioning discussions in a merge request](img/preview_issue_for_discussions.png)
Hitting **Submit issue** will cause all discussions to be marked as resolved and
add a note referring to the newly created issue.
![Mark discussions as resolved notice](img/resolve_discussion_issue_notice.png)
You can now proceed to merge the merge request from the UI.
### Moving a single discussion to a new issue
> [Introduced][ce-8266] in GitLab 9.1
To create a new issue for a single discussion, you can use the **Resolve this
discussion in a new issue** button.
![Create issue for discussion](img/new_issue_for_discussion.png)
This will direct you to a new issue prefilled with the content of the
discussion, similar to the issues created for delegating multiple
discussions at once. Saving the issue will mark the discussion as resolved and
add a note to the merge request discussion referencing the new issue.
![New issue for a single discussion](img/preview_issue_for_discussion.png)
### Only allow merge requests to be merged if all discussions are resolved
> [Introduced][ce-7125] in GitLab 8.14.
You can prevent merge requests from being merged until all discussions are
resolved.
Navigate to your project's settings page, select the
**Only allow merge requests to be merged if all discussions are resolved** check
box and hit **Save** for the changes to take effect.
![Only allow merge if all the discussions are resolved settings](img/only_allow_merge_if_all_discussions_are_resolved.png)
From now on, you will not be able to merge from the UI until all discussions
are resolved.
![Only allow merge if all the discussions are resolved message](img/only_allow_merge_if_all_discussions_are_resolved_msg.png)
## Threaded discussions
> [Introduced][ce-7527] in GitLab 9.1.
While resolvable discussions are only available to merge request diffs,
discussions can also be added without a diff. You can start a specific
discussion which will look like a thread, on issues, commits, snippets, and
merge requests.
To start a threaded discussion, click on the **Comment** button toggle dropdown,
select **Start discussion** and click **Start discussion** when you're ready to
post the comment.
![Comment type toggle](img/comment_type_toggle.gif)
This will post a comment with a single thread to allow you to discuss specific
comments in greater detail.
![Discussion comment](img/discussion_comment.png)
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
[ce-7527]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7527
[ce-7180]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7180
[ce-8266]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8266
[resolve-discussion-button]: img/resolve_discussion_button.png
[resolve-comment-button]: img/resolve_comment_button.png
[discussion-view]: img/discussion_view.png
[discussions-resolved]: img/discussions_resolved.png
[markdown]: ../markdown.md
[slash commands]: ../project/slash_commands.md
[permissions]: ../permissions.md
......@@ -34,7 +34,7 @@ Keep track of the progress during a code review with resolving comments.
Resolving comments prevents you from forgetting to address feedback and lets
you hide discussions that are no longer relevant.
[Read more about resolving discussion comments in merge requests reviews.](merge_request_discussion_resolution.md)
[Read more about resolving discussion comments in merge requests reviews.](../../discussions/index.md)
## Squash and merge
......
# Merge Request discussion resolution
> [Introduced][ce-5022] in GitLab 8.11.
Discussion resolution helps keep track of progress during code review.
Resolving comments prevents you from forgetting to address feedback and lets you
hide discussions that are no longer relevant.
!["A discussion between two people on a piece of code"][discussion-view]
Comments and discussions can be resolved by anyone with at least Developer
access to the project, as well as by the author of the merge request.
## Marking a comment or discussion as resolved
You can mark a discussion as resolved by clicking the "Resolve discussion"
button at the bottom of the discussion.
!["Resolve discussion" button][resolve-discussion-button]
Alternatively, you can mark each comment as resolved individually.
!["Resolve comment" button][resolve-comment-button]
## Jumping between unresolved discussions
When a merge request has a large number of comments it can be difficult to track
what remains unresolved. You can jump between unresolved discussions with the
Jump button next to the Reply field on a discussion.
You can also jump to the first unresolved discussion from the button next to the
resolved discussions tracker.
!["3/4 discussions resolved"][discussions-resolved]
## Only allow merge requests to be merged if all discussions are resolved
> [Introduced][ce-7125] in GitLab 8.14.
You can prevent merge requests from being merged until all discussions are
resolved.
Navigate to your project's settings page, select the
**Only allow merge requests to be merged if all discussions are resolved** check
box and hit **Save** for the changes to take effect.
![Only allow merge if all the discussions are resolved settings](img/only_allow_merge_if_all_discussions_are_resolved.png)
From now on, you will not be able to merge from the UI until all discussions
are resolved.
![Only allow merge if all the discussions are resolved message](img/only_allow_merge_if_all_discussions_are_resolved_msg.png)
## Move all unresolved discussions in a merge request to an issue
> [Introduced][ce-8266]
To continue all open discussions in a merge request, click the button **Resolve
all discussions in new issue**
![Open new issue for all unresolved discussions](img/btn_new_issue_for_all_discussions.png)
Alternatively, when your project only accepts merge requests when all discussions
are resolved, there will be an **open an issue to resolve them later** link in
the merge request-widget.
![Link in merge request widget](img/resolve_discussion_open_issue.png)
This will prepare an issue with content referring to the merge request and
discussions.
![Issue mentioning discussions in a merge request](img/preview_issue_for_discussions.png)
Hitting **Submit issue** will cause all discussions to be marked as resolved and
add a note referring to the newly created issue.
![Mark discussions as resolved notice](img/resolve_discussion_issue_notice.png)
You can now proceed to merge the merge request from the UI.
## Moving a single discussion to a new issue
> [Introduced][ce-8266]
To create a new issue for a single discussion, you can use the **Resolve this
discussion in a new issue** button.
![Create issue for discussion](img/new_issue_for_discussion.png)
This will direct you to a new issue prefilled with the content of the
discussion, similar to the issues created for delegating multiple
discussions at once.
![New issue for a single discussion](img/preview_issue_for_discussion.png)
Saving the issue will mark the discussion as resolved and add a note
to the discussion referencing the new issue.
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
[ce-7180]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7180
[ce-8266]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8266
[resolve-discussion-button]: img/resolve_discussion_button.png
[resolve-comment-button]: img/resolve_comment_button.png
[discussion-view]: img/discussion_view.png
[discussions-resolved]: img/discussions_resolved.png
This document was moved to [another location](../../discussions/index.md).
# Milestones
Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date.
A common use is keeping track of an upcoming software version. Milestones are created per-project.
You can find the milestones page under your project's **Issues ➔ Milestones**.
## Creating a milestone
To create a new milestone, simply click the **New milestone** button when in the
milestones page. A milestone can have a title, a description and start/due dates.
Once you fill in all the details, hit the **Create milestone** button.
>**Note:**
The start/due dates are required if you intend to use [Burndown charts](#burndown-charts).
![Creating a milestone](img/milestone_create.png)
## Groups and milestones
You can create a milestone for several projects in the same group simultaneously.
On the group's **Issues ➔ Milestones** page, you will be able to see the status
of that milestone across all of the selected projects. To create a new milestone
for selected projects in the group, click the **New milestone** button. The
form is the same as when creating a milestone for a specific project with the
addition of the selection of the projects you want to inherit this milestone.
![Creating a group milestone](img/milestone_group_create.png)
## Special milestone filters
In addition to the milestones that exist in the project or group, there are some
special options available when filtering by milestone:
* **No Milestone** - only show issues or merge requests without a milestone.
* **Upcoming** - show issues or merge request that belong to the next open
milestone with a due date, by project. (For example: if project A has
milestone v1 due in three days, and project B has milestone v2 due in a week,
then this will show issues or merge requests from milestone v1 in project A
and milestone v2 in project B.)
* **Started** - show issues or merge requests from any milestone with a start
date less than today. Note that this can return results from several
milestones in the same project.
## Burndown charts
>**Notes:**
- [Introduced][ee-1540] in GitLab Enterprise Edition 9.1 and is available for
[Enterprise Edition Starter][ee] users.
- Closed or reopened issues prior to GitLab 9.1 won't have a `closed_at`
value, so the burndown chart considers them as closed on the milestone
`start_date`. In that case, a warning will be displayed.
A burndown chart is available for every project milestone that has a set start
date and a set due date and is located on the project's milestone page.
It indicates the project's progress throughout that milestone (for issues that
have that milestone assigned to it). In particular, it shows how many issues
were or are still open for a given day in the milestone period. Since GitLab
only tracks when an issue was last closed (and not its full history), the chart
assumes that issue was open on days prior to that date. Reopened issues are
considered as open on one day after they were closed.
The burndown chart can also be toggled to display the cumulative open issue
weight for a given day. When using this feature, make sure your weights have
been properly assigned, since an open issue with no weight adds zero to the
cumulative value.
![burndown chart](img/burndown_chart.png)
[ee-1540]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1540
[ee]: https://about.gitlab.com/gitlab-ee
......@@ -30,12 +30,12 @@
- [Time tracking](time_tracking.md)
- [Web Editor](../user/project/repository/web_editor.md)
- [Releases](releases.md)
- [Milestones](milestones.md)
- [Milestones](../user/project/milestones/index.md)
- [Merge Requests](../user/project/merge_requests/index.md)
- [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md)
- [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md)
- [Merge when pipeline succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md)
- [Resolve discussion comments in merge requests reviews](../user/project/merge_requests/merge_request_discussion_resolution.md)
- [Resolve discussion comments in merge requests reviews](../user/discussions/index.md)
- [Resolve merge conflicts in the UI](../user/project/merge_requests/resolve_conflicts.md)
- [Revert changes in the UI](../user/project/merge_requests/revert_changes.md)
- [Merge requests versions](../user/project/merge_requests/versions.md)
......
# Milestones
Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date.
A common use is keeping track of an upcoming software version. Milestones are created per-project.
![milestone form](milestones/form.png)
## Groups and milestones
You can create a milestone for several projects in the same group simultaneously.
On the group's milestones page, you will be able to see the status of that milestone across all of the selected projects.
![group milestone form](milestones/group_form.png)
## Special milestone filters
In addition to the milestones that exist in the project or group, there are some
special options available when filtering by milestone:
* **No Milestone** - only show issues or merge requests without a milestone.
* **Upcoming** - show issues or merge request that belong to the next open
milestone with a due date, by project. (For example: if project A has
milestone v1 due in three days, and project B has milestone v2 due in a week,
then this will show issues or merge requests from milestone v1 in project A
and milestone v2 in project B.)
* **Started** - show issues or merge requests from any milestone with a start
date less than today. Note that this can return results from several
milestones in the same project.
This document was moved to [another location](../user/project/milestones/index.md).
......@@ -2,7 +2,10 @@ module EE
module Gitlab
module ServiceDesk
def self.enabled?
::License.current && ::License.current.add_on?('GitLab_ServiceDesk')
::License.current &&
::License.current.add_on?('GitLab_ServiceDesk') &&
::Gitlab::IncomingEmail.enabled? &&
::Gitlab::IncomingEmail.supports_wildcard?
end
end
end
......
......@@ -19,20 +19,17 @@ module Gitlab
private
def service_desk_key
@service_desk_key ||=
begin
mail_key =~ /\Aservice_desk[+](\w+)\z/
$1
end
return unless mail_key.include?("/")
mail_key
end
def project
return @project if instance_variable_defined?(:@project)
@project = Project.find_by(
service_desk_enabled: true,
service_desk_mail_key: service_desk_key
)
@project =
Project.where(service_desk_enabled: true)
.find_by_full_path(service_desk_key)
end
def create_issue!
......
......@@ -21,6 +21,10 @@ module Gitlab
track == 'stable'
end
def order
stable? ? 1 : 0
end
def outdated?
observed_generation < generation
end
......
......@@ -20,6 +20,7 @@ module Gitlab
return new([], valid: false) if specs.empty?
deployments = specs.map { |spec| ::Gitlab::Kubernetes::Deployment.new(spec) }
deployments.sort_by!(&:order)
new(deployments)
end
......
......@@ -2,11 +2,13 @@ require 'spec_helper'
describe Projects::ServiceDeskController do
let(:project) { create(:project_empty_repo, :private) }
let(:user) { create(:user, admin: true) }
let(:user) { create(:user) }
before do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
project.update(service_desk_enabled: true)
project.add_master(user)
sign_in(user)
......@@ -17,6 +19,7 @@ describe Projects::ServiceDeskController do
get :show, namespace_id: project.namespace.to_param, project_id: project, format: :json
body = JSON.parse(response.body)
expect(body["service_desk_address"]).to match(/\A[^@]+@[^@]+\z/)
expect(body["service_desk_enabled"]).to be_truthy
expect(response.status).to eq(200)
......@@ -38,23 +41,22 @@ describe Projects::ServiceDeskController do
describe 'PUT service desk properties' do
it 'toggles services desk incoming email' do
project.update(service_desk_enabled: true)
old_address = project.service_desk_address
project.update(service_desk_enabled: false)
put :update, namespace_id: project.namespace.to_param, project_id: project, service_desk_enabled: true, format: :json
body = JSON.parse(response.body)
expect(body["service_desk_address"]).to be_present
expect(body["service_desk_address"]).not_to eq(old_address)
expect(body["service_desk_enabled"]).to be_truthy
expect(response.status).to eq(200)
end
context 'when user is not admin' do
before { user.update(admin: false) }
context 'when user cannot admin the project' do
let(:other_user) { create(:user) }
it 'renders 404' do
sign_in(other_user)
put :update, namespace_id: project.namespace.to_param, project_id: project, service_desk_enabled: true, format: :json
expect(response.status).to eq(404)
......
......@@ -10,6 +10,7 @@ FactoryGirl.define do
trait :closed do
state :closed
closed_at Time.now
end
trait :reopened do
......
......@@ -3,7 +3,7 @@ require 'rails_helper'
describe 'Milestone show', feature: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project) }
let(:milestone) { create(:milestone, project: project, start_date: Date.today, due_date: 7.days.from_now) }
let(:labels) { create_list(:label, 2, project: project) }
let(:issue_params) { { project: project, assignee: user, author: user, milestone: milestone, labels: labels } }
......@@ -23,4 +23,47 @@ describe 'Milestone show', feature: true do
expect { visit_milestone }.not_to exceed_query_limit(control_count)
end
context 'burndown' do
let(:issue_params) { { project: project, assignee: user, author: user, milestone: milestone } }
context 'when any closed issues do not have closed_at value' do
it 'shows warning' do
create(:issue, issue_params)
create(:closed_issue, issue_params)
create(:closed_issue, issue_params.merge(closed_at: nil))
visit_milestone
expect(page).to have_selector('#data-warning', count: 1)
expect(page.find('#data-warning').text).to include("Some issues can’t be shown in the burndown chart")
expect(page).to have_selector('.burndown-chart')
end
end
context 'when all closed issues do not have closed_at value' do
it 'shows warning and hides burndown' do
create(:closed_issue, issue_params.merge(closed_at: nil))
create(:closed_issue, issue_params.merge(closed_at: nil))
visit_milestone
expect(page).to have_selector('#data-warning', count: 1)
expect(page.find('#data-warning').text).to include("The burndown chart can’t be shown")
expect(page).not_to have_selector('.burndown-chart')
end
end
context 'data is accurate' do
it 'does not show warning' do
create(:issue, issue_params)
create(:closed_issue, issue_params)
visit_milestone
expect(page).not_to have_selector('#data-warning')
expect(page).to have_selector('.burndown-chart')
end
end
end
end
......@@ -3,7 +3,6 @@ require 'spec_helper'
describe 'Service Desk Setting', js: true, feature: true do
include WaitForAjax
describe 'as project master/admin' do
let(:project) { create(:project_empty_repo, :private) }
let(:user) { create(:user) }
......@@ -12,42 +11,8 @@ describe 'Service Desk Setting', js: true, feature: true do
login_as(user)
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
end
describe 'when disabled' do
before do
visit edit_namespace_project_path(project.namespace, project)
end
it 'shows disabled activation checkbox' do
expect(page).to have_selector("#service-desk-enabled-checkbox[disabled]")
end
end
describe 'when enabled' do
before do
project.update(service_desk_enabled: true)
visit edit_namespace_project_path(project.namespace, project)
end
it 'shows disabled activation checkbox' do
expect(page).to have_selector("#service-desk-enabled-checkbox[disabled]")
end
it 'shows service_desk_address when enabled' do
expect(find('.js-service-desk-setting-wrapper .panel-body')).to have_content(project.service_desk_address)
end
end
end
describe 'as instance admin' do
let(:project) { create(:project_empty_repo, :private) }
let(:user) { create(:user, :admin) }
before do
login_as(user)
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
visit edit_namespace_project_path(project.namespace, project)
end
......@@ -61,5 +26,4 @@ describe 'Service Desk Setting', js: true, feature: true do
wait_for_ajax
expect(find('.js-service-desk-setting-wrapper .panel-body')).to have_content(project.service_desk_address)
end
end
end
......@@ -5,7 +5,7 @@ Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incom
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: incoming+service_desk+somemailkey@appmail.adventuretime.ooo
To: incoming+email/test@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: The message subject! @all
Mime-Version: 1.0
......
......@@ -36,6 +36,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
setTimeout(() => {
expect(this.component.$el.querySelector('.empty-state')).toBeDefined();
expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBe(null);
done();
}, 1);
});
......@@ -67,6 +68,8 @@ describe('Pipelines table in Commits and Merge requests', () => {
setTimeout(() => {
expect(this.component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1);
expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
expect(this.component.$el.querySelector('.empty-state')).toBe(null);
expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBe(null);
done();
}, 0);
});
......@@ -95,10 +98,12 @@ describe('Pipelines table in Commits and Merge requests', () => {
this.component.$destroy();
});
it('should render empty state', function (done) {
it('should render error state', function (done) {
setTimeout(() => {
expect(this.component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
expect(this.component.$el.querySelector('.realtime-loading')).toBe(null);
expect(this.component.$el.querySelector('.js-empty-state')).toBe(null);
expect(this.component.$el.querySelector('table')).toBe(null);
done();
}, 0);
});
......
import Vue from 'vue';
import actionsComp from '~/environments/components/environment_actions';
import actionsComp from '~/environments/components/environment_actions.vue';
describe('Actions Component', () => {
let ActionsComponent;
......
import 'timeago.js';
import Vue from 'vue';
import environmentItemComp from '~/environments/components/environment_item';
import environmentItemComp from '~/environments/components/environment_item.vue';
describe('Environment item', () => {
let EnvironmentItem;
......
import Vue from 'vue';
import environmentTableComp from '~/environments/components/environments_table';
import environmentTableComp from '~/environments/components/environments_table.vue';
describe('Environment item', () => {
let EnvironmentTable;
......
......@@ -31,12 +31,8 @@ describe('ServiceDeskSetting', () => {
el = vm.$el;
});
it('should see disabled activation checkbox', () => {
expect(vm.$refs['enabled-checkbox'].getAttribute('disabled')).toEqual('disabled');
});
it('should see only instance admin can activate/deactivate message', () => {
expect(vm.$refs['only-instance-admin-activate-message']).toBeDefined();
it('should see activation checkbox (not disabled)', () => {
expect(vm.$refs['enabled-checkbox'].getAttribute('disabled')).toEqual(null);
});
it('should see main panel with the email info', () => {
......@@ -53,20 +49,6 @@ describe('ServiceDeskSetting', () => {
expect(vm.$refs['recommend-protect-email-from-spam-message']).toBeDefined();
});
});
describe('as instance admin', () => {
beforeEach(() => {
vm = createComponent({
isEnabled: true,
isInstanceAdmin: true,
});
el = vm.$el;
});
it('should see activation checkbox (not disabled)', () => {
expect(vm.$refs['enabled-checkbox'].getAttribute('disabled')).toEqual(null);
});
});
});
describe('with incomingEmail', () => {
......
......@@ -50,22 +50,4 @@ describe('ServiceDeskStore', () => {
expect(store.state.fetchError).toEqual(err);
});
});
describe('setIsInstanceAdmin', () => {
it('defaults to false', () => {
expect(store.state.isInstanceAdmin).toEqual(false);
});
it('set true', () => {
store.setIsInstanceAdmin(true);
expect(store.state.isInstanceAdmin).toEqual(true);
});
it('set false', () => {
store.setIsInstanceAdmin(false);
expect(store.state.isInstanceAdmin).toEqual(false);
});
});
});
require 'spec_helper'
describe EE::Gitlab::ServiceDesk, lib: true do
before do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
subject { described_class.enabled? }
it { is_expected.to be_truthy }
context 'when license does not support service desk' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { false }
end
it { is_expected.to be_falsy }
end
context 'when incoming emails are disabled' do
before do
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { false }
end
it { is_expected.to be_falsy }
end
context 'when email key is not supported' do
before do
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { false }
end
it { is_expected.to be_falsy }
end
end
......@@ -9,18 +9,20 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
end
let(:email_raw) { fixture_file('emails/service_desk.eml') }
let(:project) { create(:project, :public) }
let(:namespace) { create(:namespace, name: "email") }
let(:project) { create(:project, :public, namespace: namespace, path: "test") }
context 'when service desk is enabled' do
before do
project.update(service_desk_enabled: true)
project.update(service_desk_mail_key: 'somemailkey')
allow(Notify).to receive(:service_desk_thank_you_email)
.with(kind_of(Integer)).and_return(double(deliver_later!: true))
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
it 'sends thank you the email and creates issue' do
......@@ -68,10 +70,7 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
context 'when service desk is not enabled' do
before do
project.update_attributes(
service_desk_enabled: false,
service_desk_mail_key: 'somemailkey',
)
project.update_attributes(service_desk_enabled: false)
end
it 'bounces the email' do
......
......@@ -16,14 +16,14 @@ describe Gitlab::Email::Handler, lib: true do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(true)
expect(handler_for('emails/service_desk.eml', 'service_desk+auth_token')).to be_instance_of(Gitlab::Email::Handler::EE::ServiceDeskHandler)
expect(handler_for('emails/service_desk.eml', 'service_desk+some/project')).to be_instance_of(Gitlab::Email::Handler::EE::ServiceDeskHandler)
end
it 'uses the create issue handler when Service Desk is disabled' do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(false)
expect(handler_for('emails/service_desk.eml', 'service_desk+auth_token')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
expect(handler_for('emails/service_desk.eml', 'service_desk+some/project')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
end
end
......
......@@ -53,12 +53,12 @@ describe Gitlab::Kubernetes::RolloutStatus do
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 },
{ 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 },
]
expect(rollout_status.instances).to eq(expected)
......
......@@ -57,6 +57,46 @@ describe Burndown, models: true do
expect(JSON.parse(subject).last[0]).to eq(Time.now.strftime("%Y-%m-%d"))
end
it "sets attribute accurate to true" do
burndown = described_class.new(milestone)
expect(burndown).to be_accurate
end
context "when all closed and reopened issues does not have closed_at" do
before do
milestone.issues.update_all(closed_at: nil)
end
it "considers closed_at as milestone start date" do
expect(subject).to eq([
["2017-03-01", 15, 30],
["2017-03-02", 27, 54],
["2017-03-03", 27, 54],
["2017-03-04", 27, 54],
["2017-03-05", 27, 54]
].to_json)
end
it "sets attribute empty to true" do
burndown = described_class.new(milestone)
expect(burndown).to be_empty
end
end
context "when one or more closed or reopened issues does not have closed_at" do
before do
milestone.issues.closed.first.update(closed_at: nil)
end
it "sets attribute accurate to false" do
burndown = described_class.new(milestone)
expect(burndown).not_to be_accurate
end
end
# Creates, closes and reopens issues only for odd days numbers
def build_sample
milestone.start_date.upto(milestone.due_date) do |date|
......
......@@ -116,37 +116,18 @@ describe Project, models: true do
end
end
describe '#regenerate_service_desk_key' do
describe '#service_desk_address' do
let(:project) { create(:empty_project, service_desk_enabled: true) }
before do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com")
end
subject { create(:project) }
it 'leaves it blank by default' do
expect(subject.service_desk_mail_key).to be_blank
end
it 'updates when enabled' do
subject.service_desk_enabled = true
subject.validate
expect(subject.service_desk_mail_key).not_to be_blank
end
it 'changes when enabled' do
subject.update!(service_desk_mail_key: '12345')
subject.service_desk_enabled = true
expect { subject.validate }.to change { subject.service_desk_mail_key }
end
it 'ensures mail key is never nil when enabled' do
subject.update!(service_desk_enabled: true)
expect { subject.update!(service_desk_mail_key: nil) }
.to change { subject.service_desk_mail_key }
expect(subject.service_desk_mail_key).not_to be_blank
it 'uses project full path as service desk address key' do
expect(project.service_desk_address).to eq("test+#{project.full_path}@mail.com")
end
end
end
......@@ -9,6 +9,8 @@ describe EE::NotificationService do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
def should_email!
......
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