Skip to content
Snippets Groups Projects
Commit 83408f08 authored by Douwe Maan's avatar Douwe Maan
Browse files

Merge branch '2887-promote-service-desk-feature' into 'master'

Promote EE features

Closes #2887, #2888, #2918, #2966, #2987, #2988, and #2967

See merge request !2540
parents 35ba4e76 3036a29e
No related branches found
No related tags found
1 merge request!2540Promote EE features
Pipeline #
Showing
with 289 additions and 175 deletions
......@@ -204,6 +204,7 @@ import initGroupAnalytics from './init_group_analytics';
new ProjectSelect();
break;
case 'projects:milestones:show':
new UserCallout();
case 'groups:milestones:show':
case 'dashboard:milestones:show':
new Milestone();
......@@ -290,6 +291,7 @@ import initGroupAnalytics from './init_group_analytics';
action: mrNewSubmitNode.dataset.mrSubmitAction,
});
}
new UserCallout();
case 'projects:merge_requests:creations:diffs':
case 'projects:merge_requests:edit':
new gl.Diff();
......@@ -391,7 +393,8 @@ import initGroupAnalytics from './init_group_analytics';
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
new UsersSelect();
new UserCallout('js-service-desk-callout');
new UserCallout('js-mr-approval-callout');
break;
case 'projects:imports:show':
new ProjectImport();
......@@ -541,6 +544,7 @@ import initGroupAnalytics from './init_group_analytics';
break;
case 'projects:settings:repository:show':
new UsersSelect();
new UserCallout();
// Initialize expandable settings panels
initSettingsPanels();
break;
......
/* global Chart */
export default () => {
const dataEl = document.getElementById('js-analytics-data');
const data = JSON.parse(dataEl.innerHTML);
const labels = data.labels;
const outputElIds = ['push', 'issues_closed', 'merge_requests_created'];
if (dataEl) {
const data = JSON.parse(dataEl.innerHTML);
const labels = data.labels;
const outputElIds = ['push', 'issues_closed', 'merge_requests_created'];
outputElIds.forEach((id) => {
const el = document.getElementById(id);
const ctx = el.getContext('2d');
const chart = new Chart(ctx);
outputElIds.forEach((id) => {
const el = document.getElementById(id);
const ctx = el.getContext('2d');
const chart = new Chart(ctx);
chart.Bar(
{
chart.Bar({
labels,
datasets: [{
fillColor: 'rgba(220,220,220,0.5)',
......@@ -22,13 +22,14 @@ export default () => {
data: data[id].data,
}],
},
{
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
},
);
});
{
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false,
},
);
});
$('#event-stats').tablesorter();
$('#event-stats').tablesorter();
}
};
......@@ -10,7 +10,7 @@ export default class UserCallout {
init() {
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
$('.js-close-callout').on('click', e => this.dismissCallout(e));
this.userCalloutBody.find('.js-close-callout').on('click', e => this.dismissCallout(e));
}
}
......
......@@ -17,6 +17,7 @@ $darken-border-factor: 5%;
$white-light: #fff;
$white-normal: #f0f0f0;
$white-dark: #eaeaea;
$white-transparent: rgba(255, 255, 255, 0.8);
$gray-lightest: #fdfdfd;
$gray-light: #fafafa;
......
......@@ -289,6 +289,7 @@ table.u2f-registrations {
margin: 20px -5px 0;
.bordered-box {
padding: 32px;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
background-color: $blue-25;
......@@ -296,10 +297,6 @@ table.u2f-registrations {
display: flex;
justify-content: center;
align-items: center;
}
.landing {
padding: 32px;
.close {
position: absolute;
......
.user-callout.promotion-callout {
margin: 20px 0 0;
&.prepend-top-10 {
margin-top: 10px;
}
&.append-bottom-20 {
margin-bottom: 20px;
}
.bordered-box {
padding: 20px;
border-color: $border-color;
background-color: $white-light;
align-items: flex-start;
.user-callout-copy {
max-width: 700px;
}
.close {
.dismiss-icon {
color: $gray-darkest;
}
&:hover {
.dismiss-icon {
color: $text-color;
}
}
}
.svg-container {
margin-right: 15px;
}
}
}
.promotion-modal {
.modal-dialog {
width: 540px;
}
.modal-header {
border-bottom: none;
}
.modal-body {
margin-top: -20px;
padding: 16px 16px 32px;
}
.modal-footer {
border-top: none;
}
}
.promotion-backdrop {
background-color: $white-transparent;
position: absolute;
padding-top: 72px;
.user-callout-copy {
max-width: 700px;
}
}
......@@ -30,4 +30,8 @@ def count_by_user(data)
def user_ids
@user_ids ||= @users.map(&:id)
end
def check_contribution_analytics_available!
render_404 unless @group.feature_available?(:contribution_analytics) || LicenseHelper.show_promotions?(current_user)
end
end
class Projects::AuditEventsController < Projects::ApplicationController
include LicenseHelper
before_action :authorize_admin_project!
before_action :check_audit_events_available!
......@@ -7,4 +9,8 @@ class Projects::AuditEventsController < Projects::ApplicationController
def index
@events = project.audit_events.page(params[:page])
end
def check_audit_events_available!
render_404 unless @project.feature_available?(:audit_events) || LicenseHelper.show_promotions?(current_user)
end
end
......@@ -67,5 +67,30 @@ def new_trial_url
uri.to_s
end
def upgrade_plan_url
if @project.group
group_billings_path(@project.group)
else
profile_billings_path
end
end
def show_promotions?(selected_user = current_user)
return false unless selected_user
return @show_promotions if defined?(@show_promotions)
@show_promotions =
if current_application_settings.should_check_namespace_plan?
true
else
license = License.current
license.nil? || license.expired?
end
end
def show_project_feature_promotion?(project_feature, callout_id = nil)
!@project.feature_available?(project_feature) && show_promotions? && (callout_id.nil? || show_callout?(callout_id))
end
extend self
end
- page_title "Contribution Analytics"
- header_title group_title(@group, "Contribution Analytics", group_analytics_path(@group))
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
.sub-header-block
.pull-right
.dropdown.inline
%button.dropdown-toggle.btn{ type: 'button', 'data-toggle' => 'dropdown' }
= icon('calendar-o')
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- if @group.feature_available?(:contribution_analytics)
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
.sub-header-block
.pull-right
.dropdown.inline
%button.dropdown-toggle.btn{ type: 'button', 'data-toggle' => 'dropdown' }
= icon('calendar-o')
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to group_analytics_path(@group, start_date: Date.today - 1.week) do
Last week
%li
= link_to group_analytics_path(@group, start_date: Date.today - 1.month) do
Last month
%li
= link_to group_analytics_path(@group, start_date: Date.today - 3.months) do
Last 3 months
.oneline
Contribution analytics for issues, merge requests and push events since #{@start_date}
%h3 Push
.row
.col-md-4
%ul
%li
= link_to group_analytics_path(@group, start_date: Date.today - 1.week) do
Last week
= @events.code_push.count
times
%li
= link_to group_analytics_path(@group, start_date: Date.today - 1.month) do
Last month
more than
= @events.code_push.map(&:commits_count).sum
commits
%li
= link_to group_analytics_path(@group, start_date: Date.today - 3.months) do
Last 3 months
.oneline
Contribution analytics for issues, merge requests and push events since #{@start_date}
%h3 Push
.row
.col-md-4
%ul
%li
= @events.code_push.count
times
%li
more than
= @events.code_push.map(&:commits_count).sum
commits
%li
by
= pluralize @events.code_push.pluck(:author_id).uniq.count, 'person'
by
= pluralize @events.code_push.pluck(:author_id).uniq.count, 'person'
.col-md-8
%div
%p.light Push events per group member
%canvas#push{ height: 250 }
.col-md-8
%div
%p.light Push events per group member
%canvas#push{ height: 250 }
%h3 Merge Requests
%h3 Merge Requests
.row
.col-md-4
%ul
%li
= @events.merge_requests.created.count
created
%li
= @events.merge_requests.merged.count
accepted
.row
.col-md-4
%ul
%li
= @events.merge_requests.created.count
created
%li
= @events.merge_requests.merged.count
accepted
.col-md-8
%div
%p.light Merge requests created per group member
%canvas#merge_requests_created{ height: 250 }
.col-md-8
%div
%p.light Merge requests created per group member
%canvas#merge_requests_created{ height: 250 }
%h3 Issues
%h3 Issues
.row
.col-md-4
%ul
%li
= @events.issues.created.count
created
%li
= @events.issues.closed.pluck(:target_id).uniq.count
closed
.row
.col-md-4
%ul
%li
= @events.issues.created.count
created
%li
= @events.issues.closed.pluck(:target_id).uniq.count
closed
.col-md-8
%div
%p.light Issues closed per group member
%canvas#issues_closed{ height: 250 }
.col-md-8
%div
%p.light Issues closed per group member
%canvas#issues_closed{ height: 250 }
.gray-content-block
.oneline
Contributions per group member
.gray-content-block
.oneline
Contributions per group member
.table-holder
%table.table.sortable-table#event-stats
%thead
%tr
%th.sortable
Name
= icon('sort')
%th.sortable
Pushed
= icon('sort')
%th.sortable
Opened issues
= icon('sort')
%th.sortable
Closed issues
= icon('sort')
%th.sortable
Opened MR
= icon('sort')
%th.sortable
Accepted MR
= icon('sort')
%th.sortable
Total Contributions
= icon('sort')
%tbody
- @users.each_with_index do |user, index|
.table-holder
%table.table.sortable-table#event-stats
%thead
%tr
%td
%strong
= link_to user.name, user
%td= @stats[:push][index]
%td= @stats[:issues_created][index]
%td= @stats[:issues_closed][index]
%td= @stats[:merge_requests_created][index]
%td= @stats[:merge_requests_merged][index]
%td= @stats[:total_events][index]
%th.sortable
Name
= icon('sort')
%th.sortable
Pushed
= icon('sort')
%th.sortable
Opened issues
= icon('sort')
%th.sortable
Closed issues
= icon('sort')
%th.sortable
Opened MR
= icon('sort')
%th.sortable
Accepted MR
= icon('sort')
%th.sortable
Total Contributions
= icon('sort')
%tbody
- @users.each_with_index do |user, index|
%tr
%td
%strong
= link_to user.name, user
%td= @stats[:push][index]
%td= @stats[:issues_created][index]
%td= @stats[:issues_closed][index]
%td= @stats[:merge_requests_created][index]
%td= @stats[:merge_requests_merged][index]
%td= @stats[:total_events][index]
%script#js-analytics-data{ type: "application/json" }
- data = {}
- data[:labels] = @users.map(&:name)
- [:push, :issues_closed, :merge_requests_created].each do |scope|
- data[scope] = {}
- data[scope][:data] = @stats[scope]
= data.to_json.html_safe
%script#js-analytics-data{ type: "application/json" }
- data = {}
- data[:labels] = @users.map(&:name)
- [:push, :issues_closed, :merge_requests_created].each do |scope|
- data[scope] = {}
- data[scope][:data] = @stats[scope]
= data.to_json.html_safe
- elsif show_promotions?
= render 'shared/promotions/promote_contribution_analytics'
......@@ -24,7 +24,7 @@
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
- if @group.feature_available?(:contribution_analytics)
- if @group.feature_available?(:contribution_analytics) || show_promotions?
= nav_link(path: 'analytics#show') do
= link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
%span
......
......@@ -25,7 +25,7 @@
%span
Activity
- if @group.feature_available?(:contribution_analytics)
- if @group.feature_available?(:contribution_analytics) || show_promotions?
= nav_link(path: 'analytics#show') do
= link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
%span
......
......@@ -214,6 +214,8 @@
%span
Pages
= render 'projects/settings/ee/nav'
- else
= nav_link(path: %w[members#show]) do
= link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
......
......@@ -2,7 +2,11 @@
= render "projects/settings/head"
- if show_project_feature_promotion?(:audit_events)
= render 'shared/promotions/promote_audit_events'
%h3.page-title Project Audit Events
%p.light Events in #{@project.full_path}
= render 'shared/audit_events/event_table', events: @events
- if @project.feature_available?(:audit_events)
= render 'shared/audit_events/event_table', events: @events
......@@ -163,24 +163,13 @@
%p
Customize your merge request restrictions.
.settings-content.no-animate{ class: ('expanded' if expanded) }
= render 'shared/promotions/promote_mr_features'
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
= render 'merge_request_settings', form: f
= f.submit 'Save changes', class: "btn btn-save"
- if EE::Gitlab::ServiceDesk.enabled?(project: @project)
%section.settings.js-service-desk-setting-wrapper
.settings-header
%h4
Service Desk
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p
Customize your service desk settings.
= link_to "Learn more about service desk.", help_page_path('user/project/service_desk')
.settings-content.no-animate{ class: ('expanded' if expanded) }
.js-service-desk-setting-root{ data: { endpoint: project_service_desk_path(@project),
enabled: "#{@project.service_desk_enabled}",
incoming_email: (@project.service_desk_address if @project.service_desk_enabled) } }
= render 'projects/ee/service_desk_settings'
= render 'export', project: @project
......
- if current_user && @project.feature_available?(:export_issues)
- if (current_user && @project.feature_available?(:export_issues)) || show_promotions?
%button.csv_download_link.btn.append-right-10.has-tooltip{ title: 'Export as CSV' }
= icon('download')
- return unless current_user && @project.feature_available?(:export_issues)
.issues-export-modal.modal
.modal-dialog
.modal-content
.modal-header
%a.close{ href: '#', 'data-dismiss' => 'modal' } ×
.export-svg-container.pull-right
= render 'projects/issues/export_issues/export_issues_list.svg'
%h3
Export issues
.modal-header
= icon('check', { class: 'export-checkmark' })
%strong
#{pluralize(issuables_count_for_state(:issues, params[:state]), 'issue')} selected
.modal-body
%div
The CSV export will be created in the background. Once finished, it will be sent to
%strong= @current_user.notification_email
in an attachment.
.modal-footer
= link_to 'Export issues', export_csv_project_issues_path(@project, request.query_parameters), method: :post, class: 'btn btn-success pull-left', title: 'Export issues'
- if current_user && @project.feature_available?(:export_issues)
.issues-export-modal.modal
.modal-dialog
.modal-content
.modal-header
%a.close{ href: '#', 'data-dismiss' => 'modal' } ×
.export-svg-container.pull-right
= render 'projects/issues/export_issues/export_issues_list.svg'
%h3
Export issues
.modal-header
= icon('check', { class: 'export-checkmark' })
%strong
#{pluralize(issuables_count_for_state(:issues, params[:state]), 'issue')} selected
.modal-body
%div
The CSV export will be created in the background. Once finished, it will be sent to
%strong= @current_user.notification_email
in an attachment.
.modal-footer
= link_to 'Export issues', export_csv_project_issues_path(@project, request.query_parameters), method: :post, class: 'btn btn-success pull-left', title: 'Export issues'
- elsif show_promotions?
= render 'shared/promotions/promote_csv_export'
......@@ -25,3 +25,5 @@
= render "projects/protected_tags/index"
= render @deploy_keys
= render 'shared/promotions/promote_repository_features'
<svg xmlns="http://www.w3.org/2000/svg" width="74" height="78" viewBox="0 0 74 78"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M.053 39A37.599 37.599 0 0 0 0 41c0 20.435 16.565 37 37 37s37-16.565 37-37c0-.671-.018-1.338-.053-2C72.907 58.505 56.764 74 37 74 17.236 74 1.092 58.505.053 39z"/><path fill="#EEE" fill-rule="nonzero" d="M37 70c18.225 0 33-14.775 33-33S55.225 4 37 4 4 18.775 4 37s14.775 33 33 33zm0 4C16.565 74 0 57.435 0 37S16.565 0 37 0s37 16.565 37 37-16.565 37-37 37z"/><g fill-rule="nonzero"><path fill="#E1DBF2" d="M37 49c-6.406 0-12.228-2.843-17.38-8.412a4 4 0 0 1-.267-5.113C24.53 28.559 30.434 25 37 25c6.566 0 12.47 3.56 17.647 10.475a4 4 0 0 1-.266 5.113C49.228 46.158 43.406 49 37 49zm0-4c5.225 0 10.012-2.337 14.445-7.128C46.966 31.89 42.173 29 37 29s-9.966 2.89-14.445 8.872C26.988 42.662 31.775 45 37 45z"/><path fill="#6B4FBB" d="M37 45a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm0-4a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm0-1a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></g></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" fill-rule="nonzero" d="M59.542 39.692l-12.93 15.484a2 2 0 0 1-3.36-.466L31.911 29.34l-9.073 10.532a4.007 4.007 0 0 0-2.9-2.762l10.998-12.767a2 2 0 0 1 3.341.49l11.318 25.318 9.511-11.39A3.989 3.989 0 0 0 58 40c.547 0 1.068-.11 1.542-.308z"/><circle cx="32" cy="26" r="4" fill="#6B4FBB"/><circle cx="45" cy="54" r="4" fill="#6B4FBB"/><path fill="#6B4FBB" fill-rule="nonzero" d="M19 47a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-4a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm39-1a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-4a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></g></svg>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment