Skip to content
Snippets Groups Projects
Commit 581c10e3 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 68d3f33d
No related branches found
No related tags found
No related merge requests found
Showing
with 230 additions and 84 deletions
Loading
Loading
@@ -116,16 +116,20 @@ export default {
 
// We have 10+ awarded user, join them with comma and add `and x more`.
if (remainingAwardList.length) {
title = sprintf(__(`%{listToShow}, and %{awardsListLength} more.`), {
listToShow: namesToShow.join(', '),
awardsListLength: remainingAwardList.length,
});
title = sprintf(
__(`%{listToShow}, and %{awardsListLength} more.`),
{
listToShow: namesToShow.join(', '),
awardsListLength: remainingAwardList.length,
},
false,
);
} else if (namesToShow.length > 1) {
// Join all names with comma but not the last one, it will be added with and text.
title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
// If we have more than 2 users we need an extra comma before and text.
title += namesToShow.length > 2 ? ',' : '';
title += sprintf(__(` and %{sliced}`), { sliced: namesToShow.slice(-1) }); // Append and text
title += sprintf(__(` and %{sliced}`), { sliced: namesToShow.slice(-1) }, false); // Append and text
} else {
// We have only 2 users so join them with and.
title = namesToShow.join(__(' and '));
Loading
Loading
Loading
Loading
@@ -1052,18 +1052,19 @@ class Repository
return rebase_deprecated(user, merge_request)
end
 
MergeRequest.transaction do
raw.rebase(
user,
merge_request.id,
branch: merge_request.source_branch,
branch_sha: merge_request.source_branch_sha,
remote_repository: merge_request.target_project.repository.raw,
remote_branch: merge_request.target_branch
) do |commit_id|
merge_request.update!(rebase_commit_sha: commit_id, merge_error: nil)
end
raw.rebase(
user,
merge_request.id,
branch: merge_request.source_branch,
branch_sha: merge_request.source_branch_sha,
remote_repository: merge_request.target_project.repository.raw,
remote_branch: merge_request.target_branch
) do |commit_id|
merge_request.update!(rebase_commit_sha: commit_id, merge_error: nil)
end
rescue StandardError => error
merge_request.update!(rebase_commit_sha: nil)
raise error
end
 
def squash(user, merge_request, message)
Loading
Loading
Loading
Loading
@@ -13,7 +13,6 @@ class PersonalSnippetPolicy < BasePolicy
rule { is_author | admin }.policy do
enable :read_personal_snippet
enable :update_personal_snippet
enable :destroy_personal_snippet
enable :admin_personal_snippet
enable :create_note
end
Loading
Loading
---
title: Fix award emoji tooltip being escaped twice if multiple people voted
merge_request: 19273
author: Brian T
type: fixed
---
title: Remove destroy_personal_snippet ability
merge_request: 20717
author:
type: fixed
---
title: Remove DB transaction from Rebase operation
merge_request: 20739
author:
type: fixed
Loading
Loading
@@ -31,7 +31,7 @@ enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
enable_sidekiq_memory_killer = ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.nonzero?
use_sidekiq_daemon_memory_killer = ENV["SIDEKIQ_DAEMON_MEMORY_KILLER"].to_i.nonzero?
use_sidekiq_legacy_memory_killer = !use_sidekiq_daemon_memory_killer
use_request_store = ENV['SIDEKIQ_REQUEST_STORE'].to_i.nonzero?
use_request_store = ENV.fetch('SIDEKIQ_REQUEST_STORE', 1).to_i.nonzero?
 
Sidekiq.configure_server do |config|
config.redis = queues_config_hash
Loading
Loading
Loading
Loading
@@ -51,14 +51,16 @@ def presented_no_changelog_labels
NO_CHANGELOG_LABELS.map { |label| "~#{label}" }.join(', ')
end
 
def sanitized_mr_title
gitlab.mr_json["title"].gsub(/^WIP: */, '').gsub(/`/, '\\\`')
end
changelog_needed = (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
changelog_found = git.added_files.find { |path| path =~ %r{\A(ee/)?(changelogs/unreleased)(-ee)?/} }
 
mr_title = gitlab.mr_json["title"].gsub(/^WIP: */, '')
if git.modified_files.include?("CHANGELOG.md")
fail "**CHANGELOG.md was edited.** Please remove the additions and create a CHANGELOG entry.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: mr_title, labels: presented_no_changelog_labels)
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: sanitized_mr_title, labels: presented_no_changelog_labels)
end
 
if changelog_needed
Loading
Loading
@@ -66,6 +68,6 @@ if changelog_needed
check_changelog(changelog_found)
else
message "**[CHANGELOG missing](https://docs.gitlab.com/ce/development/changelog.html)**: If this merge request [doesn't need a CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html#what-warrants-a-changelog-entry), feel free to ignore this message.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: mr_title, labels: presented_no_changelog_labels)
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: sanitized_mr_title, labels: presented_no_changelog_labels)
end
end
Loading
Loading
@@ -49,6 +49,13 @@ branch names globally in Push Rules, you can now sleep without the anxiety
of your developers' mistakes. Every branch that doesn't match your push rule
will get rejected.
 
### Custom Push Rules **(CORE ONLY)**
It's possible to create custom push rules rather than the push rules available in
**Admin area > Push Rules** by using more advanced server-side Git hooks.
See [custom server-side Git hooks](../administration/custom_hooks.md) for more information.
## Enabling push rules
 
NOTE: **Note:**
Loading
Loading
Loading
Loading
@@ -75,7 +75,7 @@ To define specs for each environment:
1. Set the status and rollout strategy of the additional spec. This status and rollout strategy combination takes precedence over the default spec since we always use the most specific match available.
1. Click **Create feature flag** or **Update feature flag**.
 
![Feature flag specs list](img/specs_list.png)
![Feature flag specs list](img/specs_list_v12_6.png)
 
NOTE: **NOTE**
We'd highly recommend you to use the [Environment](../../../ci/environments.md)
Loading
Loading
@@ -119,17 +119,15 @@ CAUTION: **Caution:**
If this strategy is selected, then the Unleash client **must** be given a user
ID for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
 
### Target users strategy
#### User IDs
 
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/8240) in GitLab 12.2.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/8240) in GitLab 12.2. [Updated](https://gitlab.com/gitlab-org/gitlab/issues/34363) to be defined per environment in GitLab 12.6.
 
A feature flag may be enabled for a list of target users. It is implemented
using the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
activation strategy.
 
The feature will always be enabled for all users in the list across all environments even if the matching environment spec **Status** is disabled.
![Feature flag target users](img/target_users_v12_2.png)
User IDs should be a comma separated list of values. For example, `user@example.com, user2@example.com`, or `username1,username2,username3`, etc.
 
CAUTION: **Caution:**
The Unleash client **must** be given a user ID for the feature to be enabled for
Loading
Loading
doc/user/project/operations/img/specs_list.png

68.5 KiB

doc/user/project/operations/img/specs_list_v12_6.png

27.6 KiB

doc/user/project/operations/img/target_users_v12_2.png

41.8 KiB

Loading
Loading
@@ -131,7 +131,7 @@ module API
snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet
 
authorize! :destroy_personal_snippet, snippet
authorize! :admin_personal_snippet, snippet
 
destroy_conditionally!(snippet)
end
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import MonitoringComponent from '~/environments/components/environment_monitoring.vue';
import Icon from '~/vue_shared/components/icon.vue';
describe('Monitoring Component', () => {
let wrapper;
const monitoringUrl = 'https://gitlab.com';
const createWrapper = () => {
wrapper = shallowMount(MonitoringComponent, {
sync: false,
attachToDocument: true,
propsData: {
monitoringUrl,
},
});
};
const findIcons = () => wrapper.findAll(Icon);
const findIconsByName = name => findIcons().filter(icon => icon.props('name') === name);
beforeEach(() => {
createWrapper();
});
describe('computed', () => {
it('title', () => {
expect(wrapper.vm.title).toBe('Monitoring');
});
});
it('should render a link to environment monitoring page', () => {
expect(wrapper.attributes('href')).toEqual(monitoringUrl);
expect(findIconsByName('chart').length).toBe(1);
expect(wrapper.attributes('data-original-title')).toBe('Monitoring');
expect(wrapper.attributes('aria-label')).toBe('Monitoring');
});
});
import $ from 'jquery';
import { shallowMount } from '@vue/test-utils';
import StopComponent from '~/environments/components/environment_stop.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import eventHub from '~/environments/event_hub';
$.fn.tooltip = () => {};
describe('Stop Component', () => {
let wrapper;
const createWrapper = () => {
wrapper = shallowMount(StopComponent, {
sync: false,
attachToDocument: true,
propsData: {
environment: {},
},
});
};
const findButton = () => wrapper.find(LoadingButton);
beforeEach(() => {
jest.spyOn(window, 'confirm');
createWrapper();
});
it('should render a button to stop the environment', () => {
expect(findButton().exists()).toBe(true);
expect(wrapper.attributes('data-original-title')).toEqual('Stop environment');
});
it('emits requestStopEnvironment in the event hub when button is clicked', () => {
jest.spyOn(eventHub, '$emit');
findButton().vm.$emit('click');
expect(eventHub.$emit).toHaveBeenCalledWith('requestStopEnvironment', wrapper.vm.environment);
});
});
import Vue from 'vue';
import terminalComp from '~/environments/components/environment_terminal_button.vue';
import { shallowMount } from '@vue/test-utils';
import TerminalComponent from '~/environments/components/environment_terminal_button.vue';
 
describe('Stop Component', () => {
let component;
let wrapper;
const terminalPath = '/path';
 
const mountWithProps = props => {
const TerminalComponent = Vue.extend(terminalComp);
component = new TerminalComponent({
wrapper = shallowMount(TerminalComponent, {
sync: false,
attachToDocument: true,
propsData: props,
}).$mount();
});
};
 
beforeEach(() => {
Loading
Loading
@@ -18,18 +19,18 @@ describe('Stop Component', () => {
 
describe('computed', () => {
it('title', () => {
expect(component.title).toEqual('Terminal');
expect(wrapper.vm.title).toEqual('Terminal');
});
});
 
it('should render a link to open a web terminal with the provided path', () => {
expect(component.$el.tagName).toEqual('A');
expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
expect(component.$el.getAttribute('href')).toEqual(terminalPath);
expect(wrapper.is('a')).toBe(true);
expect(wrapper.attributes('data-original-title')).toBe('Terminal');
expect(wrapper.attributes('aria-label')).toBe('Terminal');
expect(wrapper.attributes('href')).toBe(terminalPath);
});
 
it('should render a non-disabled button', () => {
expect(component.$el.classList).not.toContain('disabled');
expect(wrapper.classes()).not.toContain('disabled');
});
});
import Vue from 'vue';
import monitoringComp from '~/environments/components/environment_monitoring.vue';
describe('Monitoring Component', () => {
let MonitoringComponent;
let component;
const monitoringUrl = 'https://gitlab.com';
beforeEach(() => {
MonitoringComponent = Vue.extend(monitoringComp);
component = new MonitoringComponent({
propsData: {
monitoringUrl,
},
}).$mount();
});
describe('computed', () => {
it('title', () => {
expect(component.title).toEqual('Monitoring');
});
});
it('should render a link to environment monitoring page', () => {
expect(component.$el.getAttribute('href')).toEqual(monitoringUrl);
expect(component.$el.querySelector('.fa-area-chart')).toBeDefined();
expect(component.$el.getAttribute('data-original-title')).toEqual('Monitoring');
expect(component.$el.getAttribute('aria-label')).toEqual('Monitoring');
});
});
import Vue from 'vue';
import stopComp from '~/environments/components/environment_stop.vue';
describe('Stop Component', () => {
let StopComponent;
let component;
beforeEach(() => {
StopComponent = Vue.extend(stopComp);
spyOn(window, 'confirm').and.returnValue(true);
component = new StopComponent({
propsData: {
environment: {},
},
}).$mount();
});
it('should render a button to stop the environment', () => {
expect(component.$el.tagName).toEqual('BUTTON');
expect(component.$el.getAttribute('data-original-title')).toEqual('Stop environment');
});
});
Loading
Loading
@@ -61,6 +61,66 @@ describe('note_awards_list component', () => {
expect(vm.$el.querySelector('.js-add-award')).toBeDefined();
});
 
describe('when the user name contains special HTML characters', () => {
const createAwardEmoji = (_, index) => ({
name: 'art',
user: { id: index, name: `&<>"\`'-${index}`, username: `user-${index}` },
});
const mountComponent = () => {
const Component = Vue.extend(awardsNote);
vm = new Component({
store,
propsData: {
awards: awardsMock,
noteAuthorId: 0,
noteId: '545',
canAwardEmoji: true,
toggleAwardPath: '/gitlab-org/gitlab-foss/notes/545/toggle_award_emoji',
},
}).$mount();
};
const findTooltip = () =>
vm.$el.querySelector('[data-original-title]').getAttribute('data-original-title');
it('should only escape & and " characters', () => {
awardsMock = [...new Array(1)].map(createAwardEmoji);
mountComponent();
const escapedName = awardsMock[0].user.name.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
expect(vm.$el.querySelector('[data-original-title]').outerHTML).toContain(escapedName);
});
it('should not escape special HTML characters twice when only 1 person awarded', () => {
awardsMock = [...new Array(1)].map(createAwardEmoji);
mountComponent();
awardsMock.forEach(award => {
expect(findTooltip()).toContain(award.user.name);
});
});
it('should not escape special HTML characters twice when 2 people awarded', () => {
awardsMock = [...new Array(2)].map(createAwardEmoji);
mountComponent();
awardsMock.forEach(award => {
expect(findTooltip()).toContain(award.user.name);
});
});
it('should not escape special HTML characters twice when more than 10 people awarded', () => {
awardsMock = [...new Array(11)].map(createAwardEmoji);
mountComponent();
// Testing only the first 10 awards since 11 onward will not be displayed.
awardsMock.slice(0, 10).forEach(award => {
expect(findTooltip()).toContain(award.user.name);
});
});
});
describe('when the user cannot award emoji', () => {
beforeEach(() => {
const Component = Vue.extend(awardsNote);
Loading
Loading
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