提交 4038d50d 编写于 作者: F Filipa Lacerda

Merge branch 'master' into 30985-cancel-pipelines

* master:
  Fix container registry navigation menu highlights
  Resolve "Mini pipeline graph + status badge, when updating in real time don't change color and svg icon"
  Refactor group search out of global search
  disables test settings on chat notification services when repository is empty
  Disable initialization table pipeline for new merge request form
  Review changes, used eq instead of match
  Remove lighten blue and add blue-25 for background
  Fixed tests
  29595 Customize experience callout design
  Remove unneeded format block
  Fixed tests
  29595 Customize experience callout design
  Updated specs
  Remove helper
  [ci skip] Use favicon full path
  Improve gitaly_address error message
import CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
import CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
import FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
import MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
import PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
import RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
import SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
import SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
import WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
const StatusIconEntityMap = {
icon_status_canceled: CANCELED_SVG,
icon_status_created: CREATED_SVG,
icon_status_failed: FAILED_SVG,
icon_status_manual: MANUAL_SVG,
icon_status_pending: PENDING_SVG,
icon_status_running: RUNNING_SVG,
icon_status_skipped: SKIPPED_SVG,
icon_status_success: SUCCESS_SVG,
icon_status_warning: WARNING_SVG,
};
export {
CANCELED_SVG,
CREATED_SVG,
FAILED_SVG,
MANUAL_SVG,
PENDING_SVG,
RUNNING_SVG,
SKIPPED_SVG,
SUCCESS_SVG,
WARNING_SVG,
StatusIconEntityMap as default,
};
......@@ -368,9 +368,9 @@
});
};
w.gl.utils.setFavicon = (iconName) => {
if (faviconEl && iconName) {
faviconEl.setAttribute('href', `/assets/${iconName}.ico`);
w.gl.utils.setFavicon = (faviconPath) => {
if (faviconEl && faviconPath) {
faviconEl.setAttribute('href', faviconPath);
}
};
......@@ -385,8 +385,8 @@
url: pageUrl,
dataType: 'json',
success: function(data) {
if (data && data.icon) {
gl.utils.setFavicon(`ci_favicons/${data.icon}`);
if (data && data.favicon) {
gl.utils.setFavicon(data.favicon);
} else {
gl.utils.resetFavicon();
}
......
/* global Flash */
import canceledSvg from 'icons/_icon_status_canceled_borderless.svg';
import createdSvg from 'icons/_icon_status_created_borderless.svg';
import failedSvg from 'icons/_icon_status_failed_borderless.svg';
import manualSvg from 'icons/_icon_status_manual_borderless.svg';
import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
import runningSvg from 'icons/_icon_status_running_borderless.svg';
import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
import successSvg from 'icons/_icon_status_success_borderless.svg';
import warningSvg from 'icons/_icon_status_warning_borderless.svg';
import StatusIconEntityMap from '../../ci_status_icons';
export default {
data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
svg: svgsDictionary[this.stage.status.icon],
};
},
......@@ -89,6 +68,9 @@ export default {
triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
},
svgHTML() {
return StatusIconEntityMap[this.stage.status.icon];
},
},
template: `
<div>
......@@ -100,7 +82,7 @@ export default {
data-toggle="dropdown"
type="button"
:aria-label="stage.title">
<span v-html="svg" aria-hidden="true"></span>
<span v-html="svgHTML" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
......
......@@ -289,8 +289,12 @@ table.u2f-registrations {
margin: 0 auto;
.bordered-box {
border: 1px solid $border-color;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
background-color: $blue-25;
position: relative;
display: flex;
justify-content: center;
}
.landing {
......@@ -298,28 +302,59 @@ table.u2f-registrations {
margin-bottom: $gl-padding;
.close {
margin-right: 20px;
}
position: absolute;
right: 20px;
opacity: 1;
.dismiss-icon {
float: right;
cursor: pointer;
color: $blue-300;
}
.dismiss-icon {
float: right;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
&:hover {
background-color: transparent;
border: 0;
.dismiss-icon {
color: $blue-400;
}
}
}
.svg-container {
text-align: center;
margin-right: 30px;
display: inline-block;
svg {
width: 136px;
height: 136px;
height: 110px;
vertical-align: top;
}
}
.user-callout-copy {
display: inline-block;
vertical-align: top;
}
}
@media(max-width: $screen-xs-max) {
.inner-content {
padding-left: 30px;
text-align: center;
.bordered-box {
display: block;
}
.landing {
.svg-container,
.user-callout-copy {
margin: 0;
display: block;
svg {
height: 75px;
}
}
}
}
}
......@@ -22,7 +22,7 @@ class ChatNotificationService < Service
end
def can_test?
valid?
super && valid?
end
def self.supported_events
......
class StatusEntity < Grape::Entity
include RequestAwareEntity
expose :icon, :favicon, :text, :label, :group
expose :icon, :text, :label, :group
expose :has_details?, as: :has_details
expose :details_path
expose :favicon do |status|
ActionController::Base.helpers.image_path(File.join('ci_favicons', "#{status.favicon}.ico"))
end
end
......@@ -7,16 +7,13 @@ module Search
end
def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new(current_user: current_user).execute
if group
projects = projects.inside_path(group.full_path)
end
Gitlab::SearchResults.new(current_user, projects, params[:search])
end
def projects
@projects ||= ProjectsFinder.new(current_user: current_user).execute
end
def scope
@scope ||= begin
allowed_scopes = %w[issues merge_requests milestones]
......
module Search
class GroupService < Search::GlobalService
attr_accessor :group
def initialize(user, group, params)
super(user, params)
@group = group
end
def projects
return Project.none unless group
return @projects if defined? @projects
@projects = super.inside_path(group.full_path)
end
end
end
......@@ -54,6 +54,8 @@ class SearchService
Search::ProjectService.new(project, current_user, params)
elsif show_snippets?
Search::SnippetService.new(current_user, params)
elsif group
Search::GroupService.new(current_user, group, params)
else
Search::GlobalService.new(current_user, params)
end
......
......@@ -11,13 +11,13 @@
Project
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases graphs network)) do
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
= link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span
Repository
- if project_nav_tab? :container_registry
= nav_link(controller: %w(container_registry)) do
= nav_link(controller: %w[projects/registry/repositories]) do
= link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
%span
Registry
......
......@@ -46,7 +46,7 @@
-# This tab is always loaded via AJAX
- if @pipelines.any?
#pipelines.pipelines.tab-pane
= render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json))
= render 'projects/merge_requests/show/pipelines', endpoint: url_for(params.merge(format: :json)), disable_initialization: true
.mr-loading-status
= spinner
......
- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json)
- disable_initialization = local_assigns.fetch(:disable_initialization, false)
= render 'projects/commit/pipelines_list', endpoint: endpoint_path
= render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization
......@@ -3,12 +3,11 @@
%button.btn.btn-default.close.js-close-callout{ type: 'button',
'aria-label' => 'Dismiss customize experience box' }
= icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true')
.row
.col-sm-3.col-xs-12.svg-container
= custom_icon('icon_customization')
.col-sm-8.col-xs-12.inner-content
%h4
Customize your experience
%p
Change syntax themes, default project pages, and more in preferences.
= link_to 'Check it out', profile_preferences_path, class: 'btn btn-default js-close-callout'
.svg-container
= custom_icon('icon_customization')
.user-callout-copy
%h4
Customize your experience
%p
Change syntax themes, default project pages, and more in preferences.
= link_to 'Check it out', profile_preferences_path, class: 'btn btn-primary js-close-callout'
---
title: 29595 Update callout design
merge_request:
author:
---
title: Disable test settings on chat notification services when repository is empty
merge_request: 10759
author:
......@@ -15,7 +15,7 @@ module Gitlab
end
unless URI(address).scheme.in?(%w(tcp unix))
raise "Unsupported Gitaly address: #{address.inspect}"
raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'"
end
@addresses[name] = address
......
......@@ -60,7 +60,7 @@ describe Projects::BuildsController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to eq status.favicon
expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end
end
end
......@@ -1208,7 +1208,7 @@ describe Projects::MergeRequestsController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to eq status.favicon
expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end
end
......
......@@ -86,7 +86,7 @@ describe Projects::PipelinesController do
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to eq status.favicon
expect(json_response['favicon']).to eq "/assets/ci_favicons/#{status.favicon}.ico"
end
end
end
import * as icons from '~/ci_status_icons';
describe('CI status icons', () => {
const statuses = [
'canceled',
'created',
'failed',
'manual',
'pending',
'running',
'skipped',
'success',
'warning',
];
statuses.forEach((status) => {
it(`should export a ${status} svg`, () => {
const key = `${status.toUpperCase()}_SVG`;
expect(Object.hasOwnProperty.call(icons, key)).toBe(true);
expect(icons[key]).toMatch(/^<svg/);
});
});
describe('default export map', () => {
const entityIconNames = [
'icon_status_canceled',
'icon_status_created',
'icon_status_failed',
'icon_status_manual',
'icon_status_pending',
'icon_status_running',
'icon_status_skipped',
'icon_status_success',
'icon_status_warning',
];
entityIconNames.forEach((iconName) => {
it(`should have a '${iconName}' key`, () => {
expect(Object.hasOwnProperty.call(icons.default, iconName)).toBe(true);
});
});
});
});
......@@ -313,7 +313,7 @@ require('~/lib/utils/common_utils');
describe('gl.utils.setFavicon', () => {
it('should set page favicon to provided favicon', () => {
const faviconName = 'custom_favicon';
const faviconPath = '//custom_favicon';
const fakeLink = {
setAttribute() {},
};
......@@ -321,9 +321,9 @@ require('~/lib/utils/common_utils');
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href');
expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true);
expect(val.indexOf(faviconPath) > -1).toBe(true);
});
gl.utils.setFavicon(faviconName);
gl.utils.setFavicon(faviconPath);
});
});
......@@ -345,13 +345,12 @@ require('~/lib/utils/common_utils');
describe('gl.utils.setCiStatusFavicon', () => {
it('should set page favicon to CI status favicon based on provided status', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
const FAVICON_PATH = 'ci_favicons/';
const FAVICON = 'icon_status_success';
const FAVICON_PATH = '//icon_status_success';
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
spyOn($, 'ajax').and.callFake(function (options) {
options.success({ icon: FAVICON });
expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON);
options.success({ favicon: FAVICON_PATH });
expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH);
options.success();
expect(spyResetFavicon).toHaveBeenCalled();
options.error();
......
......@@ -14,7 +14,6 @@ describe('UserCallout', function () {
this.userCallout = new UserCallout();
this.closeButton = $('.js-close-callout.close');
this.userCalloutBtn = $('.js-close-callout:not(.close)');
this.userCalloutContainer = $('.user-callout');
});
it('hides when user clicks on the dismiss-icon', (done) => {
......
import Vue from 'vue';
import { SUCCESS_SVG } from '~/ci_status_icons';
import Stage from '~/vue_pipelines_index/components/stage';
function minify(string) {
return string.replace(/\s/g, '');
}
describe('Pipelines Stage', () => {
describe('data', () => {
let stageReturnValue;
beforeEach(() => {
stageReturnValue = Stage.data();
});
it('should return object with .builds and .spinner', () => {
expect(stageReturnValue).toEqual({
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
});
});
});
describe('computed', () => {
describe('svgHTML', function () {
let stage;
let svgHTML;
beforeEach(() => {
stage = { stage: { status: { icon: 'icon_status_success' } } };
svgHTML = Stage.computed.svgHTML.call(stage);
});
it("should return the correct icon for the stage's status", () => {
expect(svgHTML).toBe(SUCCESS_SVG);
});
});
});
describe('when mounted', () => {
let StageComponent;
let renderedComponent;
let stage;
beforeEach(() => {
stage = { status: { icon: 'icon_status_success' } };
StageComponent = Vue.extend(Stage);
renderedComponent = new StageComponent({
propsData: {
stage,
},
}).$mount();
});
it('should render the correct status svg', () => {
const minifiedComponent = minify(renderedComponent.$el.outerHTML);
const expectedSVG = minify(SUCCESS_SVG);
expect(minifiedComponent).toContain(expectedSVG);
});
});
});
require 'spec_helper'
describe ChatNotificationService, models: true do
describe "Associations" do
describe 'Associations' do
before do
allow(subject).to receive(:activated?).and_return(true)
end
it { is_expected.to validate_presence_of :webhook }
end
describe '#can_test?' do
context 'with empty repository' do
it 'returns false' do
subject.project = create(:empty_project, :empty_repo)
expect(subject.can_test?).to be false
end
end
context 'with repository' do
it 'returns true' do
subject.project = create(:project)
expect(subject.can_test?).to be true
end
end
end
end
......@@ -38,7 +38,7 @@ describe BuildSerializer do
expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq(status.label)
expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to eq(status.favicon)
expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico")
end
end
end
......
......@@ -144,7 +144,7 @@ describe PipelineSerializer do
expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq(status.label)
expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to eq(status.favicon)
expect(subject[:favicon]).to eq("/assets/ci_favicons/#{status.favicon}.ico")
end
end
end
......
......@@ -40,27 +40,6 @@ describe Search::GlobalService, services: true do
expect(results.objects('projects')).to match_array [found_project]
end
context 'nested group' do
let!(:nested_group) { create(:group, :nested) }
let!(:project) { create(:empty_project, namespace: nested_group) }
before do
project.add_master(user)
end
it 'returns result from nested group' do
results = Search::GlobalService.new(user, search: project.path).execute
expect(results.objects('projects')).to match_array [project]
end
it 'returns result from descendants when search inside group' do
results = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent).execute
expect(results.objects('projects')).to match_array [project]
end
end
end
end
end
require 'spec_helper'
describe Search::GroupService, services: true do
shared_examples_for 'group search' do
context 'finding projects by name' do
let(:user) { create(:user) }
let(:term) { "Project Name" }
let(:nested_group) { create(:group, :nested) }
# These projects shouldn't be found
let!(:outside_project) { create(:empty_project, :public, name: "Outside #{term}") }
let!(:private_project) { create(:empty_project, :private, namespace: nested_group, name: "Private #{term}" )}
let!(:other_project) { create(:empty_project, :public, namespace: nested_group, name: term.reverse) }
# These projects should be found
let!(:project1) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 1") }
let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") }
let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") }
let(:results) { Search::GroupService.new(user, search_group, search: term).execute }
subject { results.objects('projects') }
context 'in parent group' do
let(:search_group) { nested_group.parent }
it { is_expected.to match_array([project1, project2, project3]) }
end
context 'in subgroup' do
let(:search_group) { nested_group }
it { is_expected.to match_array([project1, project2]) }
end
end
end
describe 'basic search' do
include_examples 'group search'
end
end
require 'spec_helper'
describe 'layouts/nav/_project' do
describe 'container registry tab' do
before do
stub_container_registry_config(enabled: true)
assign(:project, create(:project))
allow(view).to receive(:current_ref).and_return('master')
allow(view).to receive(:can?).and_return(true)
allow(controller).to receive(:controller_name)
.and_return('repositories')
allow(controller).to receive(:controller_path)
.and_return('projects/registry/repositories')
end
it 'has both Registry and Repository tabs' do
render
expect(rendered).to have_text 'Repository'
expect(rendered).to have_text 'Registry'
end
it 'highlights only one tab' do
render
expect(rendered).to have_css('.active', count: 1)
end
it 'highlights container registry tab only' do
render
expect(rendered).to have_css('.active', text: 'Registry')
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册