Commit 8e554b7b authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 43030472 07cc1885
/* eslint-disable class-methods-use-this */
const defaultTimezone = 'UTC';
export const formatUtcOffset = offset => {
const parsed = parseInt(offset, 10);
if (Number.isNaN(parsed) || parsed === 0) {
return `0`;
}
const prefix = offset > 0 ? '+' : '-';
return `${prefix} ${Math.abs(offset / 3600)}`;
};
import $ from 'jquery';
export const formatTimezone = item => `[UTC ${formatUtcOffset(item.offset)}] ${item.name}`;
const defaultTimezone = 'UTC';
const defaults = {
$inputEl: null,
$dropdownEl: null,
onSelectTimezone: null,
};
export default class TimezoneDropdown {
constructor() {
this.$dropdown = $('.js-timezone-dropdown');
constructor({ $dropdownEl, $inputEl, onSelectTimezone } = defaults) {
this.$dropdown = $dropdownEl;
this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text');
this.$input = $('#schedule_cron_timezone');
this.$input = $inputEl;
this.timezoneData = this.$dropdown.data('data');
this.initDefaultTimezone();
this.initDropdown();
this.onSelectTimezone = onSelectTimezone;
}
initDropdown() {
......@@ -24,28 +40,12 @@ export default class TimezoneDropdown {
fields: ['name'],
},
clicked: cfg => this.updateInputValue(cfg),
text: item => this.formatTimezone(item),
text: item => formatTimezone(item),
});
this.setDropdownToggle();
}
formatUtcOffset(offset) {
let prefix = '';
if (offset > 0) {
prefix = '+';
} else if (offset < 0) {
prefix = '-';
}
return `${prefix} ${Math.abs(offset / 3600)}`;
}
formatTimezone(item) {
return `[UTC ${this.formatUtcOffset(item.offset)}] ${item.name}`;
}
initDefaultTimezone() {
const initialValue = this.$input.val();
......@@ -56,13 +56,14 @@ export default class TimezoneDropdown {
setDropdownToggle() {
const initialValue = this.$input.val();
this.$dropdownToggle.text(initialValue);
}
updateInputValue({ selectedObj, e }) {
e.preventDefault();
this.$input.val(selectedObj.identifier);
gl.pipelineScheduleFieldErrors.updateFormValidityState();
if (this.onSelectTimezone) {
this.onSelectTimezone({ selectedObj, e });
}
}
}
......@@ -41,7 +41,13 @@ export default () => {
const formElement = document.getElementById('new-pipeline-schedule-form');
gl.timezoneDropdown = new TimezoneDropdown();
gl.timezoneDropdown = new TimezoneDropdown({
$dropdownEl: $('.js-timezone-dropdown'),
$inputEl: $('#schedule_cron_timezone'),
onSelectTimezone: () => {
gl.pipelineScheduleFieldErrors.updateFormValidityState();
},
});
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
......
......@@ -96,14 +96,27 @@ module QuickActions
end
def find_labels(labels_params = nil)
extract_references(labels_params, :label) | find_labels_by_name_no_tilde(labels_params)
end
def find_labels_by_name_no_tilde(labels_params)
return Label.none if label_with_tilde?(labels_params)
finder_params = { include_ancestor_groups: true }
finder_params[:project_id] = project.id if project
finder_params[:group_id] = group.id if group
finder_params[:name] = labels_params.split if labels_params
finder_params[:name] = extract_label_names(labels_params) if labels_params
result = LabelsFinder.new(current_user, finder_params).execute
LabelsFinder.new(current_user, finder_params).execute
end
def label_with_tilde?(labels_params)
labels_params&.include?('~')
end
extract_references(labels_params, :label) | result
def extract_label_names(labels_params)
# '"A" "A B C" A B' => ["A", "A B C", "A", "B"]
labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact
end
def find_label_references(labels_param)
......
---
title: Fix quick actions add label name middle word overlaps
merge_request: 26602
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Extend timezone dropdown
merge_request: 26311
author:
type: changed
......@@ -31,7 +31,7 @@ discussions, and descriptions:
| `/reassign @user1 @user2` | Change assignee | ✓ | ✓ |
| `/milestone %milestone` | Set milestone | ✓ | ✓ |
| `/remove_milestone` | Remove milestone | ✓ | ✓ |
| `/label ~label1 ~label2` | Add label(s) | ✓ | ✓ |
| `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ |
| `/relabel ~label1 ~label2` | Replace label | ✓ | ✓ |
| <code>/copy_metadata #issue &#124; !merge_request</code> | Copy labels and milestone from other issue or merge request | ✓ | ✓ |
......
import $ from 'jquery';
import GLDropdown from '~/gl_dropdown'; // eslint-disable-line no-unused-vars
import TimezoneDropdown, {
formatUtcOffset,
formatTimezone,
} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown';
describe('Timezone Dropdown', function() {
preloadFixtures('pipeline_schedules/edit.html');
let $inputEl = null;
let $dropdownEl = null;
let $wrapper = null;
const tzListSel = '.dropdown-content ul li a.is-active';
describe('Initialize', () => {
describe('with dropdown already loaded', () => {
beforeEach(() => {
loadFixtures('pipeline_schedules/edit.html');
$wrapper = $('.dropdown');
$inputEl = $('#schedule_cron_timezone');
$dropdownEl = $('.js-timezone-dropdown');
// eslint-disable-next-line no-new
new TimezoneDropdown({
$inputEl,
$dropdownEl,
});
});
it('can take an $inputEl in the constructor', () => {
const tzStr = '[UTC + 5.5] Sri Jayawardenepura';
const tzValue = 'Asia/Colombo';
expect($inputEl.val()).toBe('UTC');
$(`${tzListSel}:contains('${tzStr}')`, $wrapper).trigger('click');
const val = $inputEl.val();
expect(val).toBe(tzValue);
expect(val).not.toBe('UTC');
});
it('will format data array of timezones into a list of offsets', () => {
const data = $dropdownEl.data('data');
const formatted = $wrapper.find(tzListSel).text();
data.forEach(item => {
expect(formatted).toContain(formatTimezone(item));
});
});
it('will default the timezone to UTC', () => {
const tz = $inputEl.val();
expect(tz).toBe('UTC');
});
});
describe('without dropdown loaded', () => {
beforeEach(() => {
loadFixtures('pipeline_schedules/edit.html');
$wrapper = $('.dropdown');
$inputEl = $('#schedule_cron_timezone');
$dropdownEl = $('.js-timezone-dropdown');
});
it('will populate the list of UTC offsets after the dropdown is loaded', () => {
expect($wrapper.find(tzListSel).length).toEqual(0);
// eslint-disable-next-line no-new
new TimezoneDropdown({
$inputEl,
$dropdownEl,
});
expect($wrapper.find(tzListSel).length).toEqual($($dropdownEl).data('data').length);
});
it('will call a provided handler when a new timezone is selected', () => {
const onSelectTimezone = jasmine.createSpy('onSelectTimezoneMock');
// eslint-disable-next-line no-new
new TimezoneDropdown({
$inputEl,
$dropdownEl,
onSelectTimezone,
});
$wrapper
.find(tzListSel)
.first()
.trigger('click');
expect(onSelectTimezone).toHaveBeenCalled();
});
});
});
describe('formatUtcOffset', () => {
it('will convert negative utc offsets in seconds to hours and minutes', () => {
expect(formatUtcOffset(-21600)).toEqual('- 6');
});
it('will convert positive utc offsets in seconds to hours and minutes', () => {
expect(formatUtcOffset(25200)).toEqual('+ 7');
expect(formatUtcOffset(49500)).toEqual('+ 13.75');
});
it('will return 0 when given a string', () => {
expect(formatUtcOffset('BLAH')).toEqual('0');
expect(formatUtcOffset('$%$%')).toEqual('0');
});
it('will return 0 when given an array', () => {
expect(formatUtcOffset(['an', 'array'])).toEqual('0');
});
it('will return 0 when given an object', () => {
expect(formatUtcOffset({ some: '', object: '' })).toEqual('0');
});
it('will return 0 when given null', () => {
expect(formatUtcOffset(null)).toEqual('0');
});
it('will return 0 when given undefined', () => {
expect(formatUtcOffset(undefined)).toEqual('0');
});
it('will return 0 when given empty input', () => {
expect(formatUtcOffset('')).toEqual('0');
});
});
describe('formatTimezone', () => {
it('given name: "Chatham Is.", offset: "49500", will format for display as "[UTC + 13.75] Chatham Is."', () => {
expect(
formatTimezone({
name: 'Chatham Is.',
offset: 49500,
identifier: 'Pacific/Chatham',
}),
).toEqual('[UTC + 13.75] Chatham Is.');
});
it('given name: "Saskatchewan", offset: "-21600", will format for display as "[UTC - 6] Saskatchewan"', () => {
expect(
formatTimezone({
name: 'Saskatchewan',
offset: -21600,
identifier: 'America/Regina',
}),
).toEqual('[UTC - 6] Saskatchewan');
});
it('given name: "Accra", offset: "0", will format for display as "[UTC 0] Accra"', () => {
expect(
formatTimezone({
name: 'Accra',
offset: 0,
identifier: 'Africa/Accra',
}),
).toEqual('[UTC 0] Accra');
});
});
});
......@@ -10,6 +10,7 @@ describe QuickActions::InterpretService do
let(:milestone) { create(:milestone, project: project, title: '9.10') }
let(:commit) { create(:commit, project: project) }
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
let(:helmchart) { create(:label, project: project, title: 'Helm Chart Registry') }
let(:bug) { create(:label, project: project, title: 'Bug') }
let(:note) { build(:note, commit_id: merge_request.diff_head_sha) }
let(:service) { described_class.new(project, developer) }
......@@ -94,6 +95,26 @@ describe QuickActions::InterpretService do
end
end
shared_examples 'multiword label name starting without ~' do
it 'fetches label ids and populates add_label_ids if content contains /label' do
helmchart # populate the label
_, updates = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [helmchart.id])
end
end
shared_examples 'label name is included in the middle of another label name' do
it 'ignores the sublabel when the content contains the includer label name' do
helmchart # populate the label
create(:label, project: project, title: 'Chart')
_, updates = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [helmchart.id])
end
end
shared_examples 'unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
......@@ -664,6 +685,26 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
it_behaves_like 'multiword label name starting without ~' do
let(:content) { %(/label "#{helmchart.title}") }
let(:issuable) { issue }
end
it_behaves_like 'multiword label name starting without ~' do
let(:content) { %(/label "#{helmchart.title}") }
let(:issuable) { merge_request }
end
it_behaves_like 'label name is included in the middle of another label name' do
let(:content) { %(/label ~"#{helmchart.title}") }
let(:issuable) { issue }
end
it_behaves_like 'label name is included in the middle of another label name' do
let(:content) { %(/label ~"#{helmchart.title}") }
let(:issuable) { merge_request }
end
it_behaves_like 'unlabel command' do
let(:content) { %(/unlabel ~"#{inprogress.title}") }
let(:issuable) { issue }
......
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