Commit d66342f2 authored by Ruben Davila's avatar Ruben Davila

Merge remote-tracking branch 'ce/8-12-stable' into 8-12-stable-ee

Conflicts:
	VERSION
parents 8a0ad9b6 afd43301
Please view this file on the master branch, on stable branches it's out of date.
v 8.12.1 (unreleased)
v 8.12.2 (unreleased)
v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
- Fix issue with search filter labels not displaying
v 8.12.0
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
......
((global) => {
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
const store = gl.cycleAnalyticsStore = {
isLoading: true,
hasError: false,
isHelpDismissed: $.cookie(COOKIE_NAME),
analytics: {}
};
gl.CycleAnalytics = class CycleAnalytics {
constructor() {
const that = this;
this.isHelpDismissed = $.cookie(COOKIE_NAME);
this.vue = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
created: this.fetchData(),
data: this.decorateData({ isLoading: true }),
data: store,
methods: {
dismissLanding() {
that.dismissLanding();
......@@ -21,6 +26,7 @@
}
fetchData(options) {
store.isLoading = true;
options = options || { startDate: 30 };
$.ajax({
......@@ -30,22 +36,20 @@
contentType: 'application/json',
data: { start_date: options.startDate }
}).done((data) => {
this.vue.$data = this.decorateData(data);
this.decorateData(data);
this.initDropdown();
})
.error((data) => {
this.handleError(data);
})
.always(() => {
this.vue.isLoading = false;
store.isLoading = false;
})
}
decorateData(data) {
data.summary = data.summary || [];
data.stats = data.stats || [];
data.isHelpDismissed = this.isHelpDismissed;
data.isLoading = data.isLoading || false;
data.summary.forEach((item) => {
item.value = item.value || '-';
......@@ -53,23 +57,21 @@
data.stats.forEach((item) => {
item.value = item.value || '- - -';
})
});
return data;
store.analytics = data;
}
handleError(data) {
this.vue.$data = {
hasError: true,
isHelpDismissed: this.isHelpDismissed
};
store.hasError = true;
new Flash('There was an error while fetching cycle analytics data.', 'alert');
}
dismissLanding() {
this.vue.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true);
store.isHelpDismissed = true;
$.cookie(COOKIE_NAME, true, {
path: gon.relative_url_root || '/'
});
}
initDropdown() {
......@@ -82,7 +84,6 @@
const value = $target.data('value');
$label.text($target.text().trim());
this.vue.isLoading = true;
this.fetchData({ startDate: value });
})
}
......
......@@ -269,6 +269,12 @@ $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: $gray-light;
/*
* Cycle Analytics
*/
$cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c;
/*
* Personal Access Tokens
*/
......
#cycle-analytics {
margin: 24px auto 0;
width: 800px;
max-width: 800px;
position: relative;
.panel {
......@@ -9,10 +9,18 @@
padding: 24px 0;
border-bottom: none;
position: relative;
@media (max-width: $screen-sm-min) {
padding: 6px 0 24px;
}
}
.column {
text-align: center;
@media (max-width: $screen-sm-min) {
padding: 15px 0;
}
.header {
font-size: 30px;
......@@ -28,11 +36,14 @@
&:last-child {
text-align: right;
@media (max-width: $screen-sm-min) {
text-align: center;
}
}
}
.dropdown {
position: relative;
top: 13px;
}
}
......@@ -40,7 +51,7 @@
.bordered-box {
border: 1px solid $border-color;
@include border-radius($border-radius-default);
position: relative;
}
.content-list {
......@@ -60,9 +71,15 @@
line-height: 19px;
font-size: 15px;
font-weight: 600;
color: $gl-title-color;
}
&:text {
color: #8c8c8c;
&.text {
color: $layout-link-gray;
&.value-col {
color: $gl-title-color;
}
}
}
}
......@@ -71,7 +88,9 @@
text-align: right;
span {
line-height: 42px;
position: relative;
vertical-align: middle;
top: 3px;
}
}
}
......@@ -82,21 +101,25 @@
.dismiss-icon {
position: absolute;
right: $gl-padding;
right: $cycle-analytics-box-padding;
cursor: pointer;
color: #b2b2b2;
}
svg {
margin: 0 20px;
float: left;
width: 136px;
height: 136px;
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
.inner-content {
width: 480px;
float: left;
@media (max-width: $screen-sm-min) {
padding: 0 28px;
text-align: center;
}
h4 {
color: $gl-text-color;
......@@ -104,7 +127,7 @@
}
p {
color: #8c8c8c;
color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding;
}
}
......
......@@ -6,8 +6,6 @@ class SearchController < ApplicationController
layout 'search'
def show
return if params[:search].nil? || params[:search].blank?
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project)
......@@ -18,6 +16,8 @@ class SearchController < ApplicationController
@group = nil unless can?(current_user, :read_group, @group)
end
return if params[:search].nil? || params[:search].blank?
@search_term = params[:search]
@scope = params[:scope]
......
......@@ -2,18 +2,20 @@
- page_title "Cycle Analytics"
= render "projects/pipelines/head"
#cycle-analytics{"v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
= icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
= custom_icon('icon_cycle_analytics_splash')
.inner-content
%h4
Introducing Cycle Analytics
%p
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
.row
.col-sm-3.col-xs-12.svg-container
= custom_icon('icon_cycle_analytics_splash')
.col-sm-8.col-xs-12.inner-content
%h4
Introducing Cycle Analytics
%p
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.
= link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
= link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn'
= icon("spinner spin", "v-show" => "isLoading")
......@@ -25,11 +27,11 @@
.content-block
.container-fluid
.row
.col-xs-3.column{"v-for" => "item in summary"}
.col-sm-3.col-xs-12.column{"v-for" => "item in analytics.summary"}
%h3.header {{item.value}}
%p.text {{item.title}}
.col-xs-3.column
.col-sm-3.col-xs-12.column
.dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"}
%span.dropdown-label Last 30 days
......@@ -44,14 +46,14 @@
.bordered-box
%ul.content-list
%li{"v-for" => "item in stats"}
%li{"v-for" => "item in analytics.stats"}
.container-fluid
.row
.col-xs-10.title-col
.col-xs-8.title-col
%p.title
{{item.title}}
%p.text
{{item.description}}
.col-xs-2.value-col
.col-xs-4.value-col
%span
{{item.value}}
......@@ -10,6 +10,7 @@ following locations:
- [Award Emoji](award_emoji.md)
- [Branches](branches.md)
- [Broadcast Messages](broadcast_messages.md)
- [Builds](builds.md)
- [Build Triggers](build_triggers.md)
- [Build Variables](build_variables.md)
......
......@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist
whitelist = super.dup
whitelist = super
customize_whitelist(whitelist)
......@@ -42,58 +42,58 @@ module Banzai
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
whitelist[:transformers] = whitelist[:transformers].dup
# ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_unsafe_links)
whitelist[:transformers].push(self.class.remove_unsafe_links)
# Remove `rel` attribute from `a` elements
whitelist[:transformers].push(remove_rel)
whitelist[:transformers].push(self.class.remove_rel)
# Remove `class` attribute from non-highlight spans
whitelist[:transformers].push(clean_spans)
whitelist[:transformers].push(self.class.clean_spans)
whitelist
end
def remove_unsafe_links
lambda do |env|
node = env[:node]
class << self
def remove_unsafe_links
lambda do |env|
node = env[:node]
return unless node.name == 'a'
return unless node.has_attribute?('href')
return unless node.name == 'a'
return unless node.has_attribute?('href')
begin
uri = Addressable::URI.parse(node['href'])
uri.scheme = uri.scheme.strip.downcase if uri.scheme
begin
uri = Addressable::URI.parse(node['href'])
uri.scheme = uri.scheme.strip.downcase if uri.scheme
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError
node.remove_attribute('href')
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError
node.remove_attribute('href')
end
end
end
end
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
env[:node].remove_attribute('rel')
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
env[:node].remove_attribute('rel')
end
end
end
end
def clean_spans
lambda do |env|
node = env[:node]
def clean_spans
lambda do |env|
node = env[:node]
return unless node.name == 'span'
return unless node.has_attribute?('class')
return unless node.name == 'span'
return unless node.has_attribute?('class')
unless has_ancestor?(node, 'pre')
node.remove_attribute('class')
end
unless node.ancestors.any? { |n| n.name.casecmp('pre').zero? }
node.remove_attribute('class')
end
{ node_whitelist: [node] }
{ node_whitelist: [node] }
end
end
end
end
......
require 'spec_helper'
describe "Search", feature: true do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let!(:issue) { create(:issue, project: project, assignee: user) }
......@@ -16,6 +18,36 @@ describe "Search", feature: true do
expect(page).not_to have_selector('.search')
end
context 'search filters', js: true do
let(:group) { create(:group) }
before do
group.add_owner(user)
end
it 'shows group name after filtering' do
find('.js-search-group-dropdown').click
wait_for_ajax
page.within '.search-holder' do
click_link group.name
end
expect(find('.js-search-group-dropdown')).to have_content(group.name)
end
it 'shows project name after filtering' do
page.within('.project-filter') do
find('.js-search-project-dropdown').click
wait_for_ajax
click_link project.name_with_namespace
end
expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
end
end
describe 'searching for Projects' do
it 'finds a project' do
page.within '.search-holder' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment