Commit 1bab0ba5 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 2d03845a
......@@ -636,19 +636,12 @@ entry.
## 12.1.12
<<<<<<< HEAD
### Security (11 changes)
=======
### Security (12 changes)
>>>>>>> master
- Add a policy check for system notes that may not be visible due to cross references to private items.
- Display only participants that user has permission to see on milestone page.
- Do not disclose project milestones on group milestones page when project milestones access is disabled in project settings.
<<<<<<< HEAD
=======
- Check permissions before showing head pipeline blocking merge requests.
>>>>>>> master
- Fix new project path being disclosed through unsubscribe link of issue/merge requests.
- Prevent bypassing email verification using Salesforce.
- Do not show resource label events referencing not accessible labels.
......
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { s__ } from '~/locale';
export default {
name: 'MilestoneList',
components: {
GlLink,
Icon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
milestones: {
type: Array,
required: true,
},
},
computed: {
labelText() {
return this.milestones.length === 1 ? s__('Milestone') : s__('Milestones');
},
},
};
</script>
<template>
<div>
<icon name="flag" class="align-middle" /> <span class="js-label-text">{{ labelText }}</span>
<template v-for="(milestone, index) in milestones">
<gl-link
:key="milestone.id"
v-gl-tooltip
:title="milestone.description"
:href="milestone.web_url"
>
{{ milestone.title }}
</gl-link>
<template v-if="index !== milestones.length - 1">
&bull;
</template>
</template>
</div>
</template>
......@@ -5,8 +5,7 @@ import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import MilestoneList from './milestone_list.vue';
import { __, sprintf } from '../../locale';
import { __, n__, sprintf } from '../../locale';
export default {
name: 'ReleaseBlock',
......@@ -15,7 +14,6 @@ export default {
GlBadge,
Icon,
UserAvatarLink,
MilestoneList,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -57,19 +55,11 @@ export default {
hasAuthor() {
return !_.isEmpty(this.author);
},
milestones() {
// At the moment, a release can only be associated to
// one milestone. This will be expanded to be many-to-many
// in the near future, so we pass the milestone as an
// array here in anticipation of this change.
return [this.release.milestone];
},
shouldRenderMilestones() {
// Similar to the `milestones` computed above,
// this check will need to be updated once
// the API begins sending an array of milestones
// instead of just a single object.
return Boolean(this.release.milestone);
return !_.isEmpty(this.release.milestones);
},
labelText() {
return n__('Milestone', 'Milestones', this.release.milestones.length);
},
},
};
......@@ -101,11 +91,27 @@ export default {
<span v-else v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span>
</div>
<milestone-list
v-if="shouldRenderMilestones"
class="append-right-4 js-milestone-list"
:milestones="milestones"
/>
<template v-if="shouldRenderMilestones">
<div class="js-milestone-list-label">
<icon name="flag" class="align-middle" />
<span class="js-label-text">{{ labelText }}</span>
</div>
<template v-for="(milestone, index) in release.milestones">
<gl-link
:key="milestone.id"
v-gl-tooltip
:title="milestone.description"
:href="milestone.web_url"
class="append-right-4 prepend-left-4 js-milestone-link"
>
{{ milestone.title }}
</gl-link>
<template v-if="index !== release.milestones.length - 1">
&bull;
</template>
</template>
</template>
<div class="append-right-4">
&bull;
......
- if @project.pages_deployed?
.card
.card-header
Access pages
= s_('GitLabPages|Access pages')
.card-body
%p
%strong
= _("Your pages are served under:")
= s_('GitLabPages|Your pages are served under:')
%p
= external_link(@project.pages_url, @project.pages_url)
......@@ -14,4 +14,4 @@
%p
= external_link(domain.url, domain.url)
.card-footer.alert-primary
= _("It may take up to 30 minutes before the site is available after the first deployment.")
= s_('GitLabPages|It may take up to 30 minutes before the site is available after the first deployment.')
- if @project.pages_deployed?
- if can?(current_user, :remove_pages, @project)
.card.border-danger
.card-header.bg-danger.text-white Remove pages
.card-header.bg-danger.text-white
= s_('GitLabPages|Remove pages')
.errors-holder
.card-body
%p
Removing pages will prevent them from being exposed to the outside world.
= s_('GitLabPages|Removing pages will prevent them from being exposed to the outside world.')
.form-actions
= link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
= link_to s_('GitLabPages|Remove pages'), project_pages_path(@project), data: { confirm: s_('GitLabPages|Are you sure?')}, method: :delete, class: "btn btn-remove"
- else
.nothing-here-block Only project maintainers can remove pages
.nothing-here-block
= s_('GitLabPages|Only project maintainers can remove pages')
......@@ -3,8 +3,9 @@
.form-check
= f.check_box :pages_https_only, class: 'form-check-input', disabled: pages_https_only_disabled?
= f.label :pages_https_only, class: pages_https_only_label_class do
%strong Force HTTPS (requires valid certificates)
%strong
= s_('GitLabPages|Force HTTPS (requires valid certificates)')
- unless pages_https_only_disabled?
.prepend-top-10
= f.submit 'Save', class: 'btn btn-success'
= f.submit s_('GitLabPages|Save'), class: 'btn btn-success'
......@@ -8,20 +8,25 @@
- @domains.each do |domain|
%li.pages-domain-list-item.list-group-item.d-flex.justify-content-between
- if verification_enabled
- tooltip, status = domain.unverified? ? [_('Unverified'), 'failed'] : [_('Verified'), 'success']
- tooltip, status = domain.unverified? ? [s_('GitLabPages|Unverified'), 'failed'] : [s_('GitLabPages|Verified'), 'success']
.domain-status.ci-status-icon.has-tooltip{ class: "ci-status-icon-#{status}", title: tooltip }
= sprite_icon("status_#{status}", size: 16 )
.domain-name
= external_link(domain.url, domain.url)
- if domain.subject
%div
%span.badge.badge-gray Certificate: #{domain.subject}
%span.badge.badge-gray
= s_('GitLabPages|Certificate: %{subject}') % { subject: domain.subject }
- if domain.expired?
%span.badge.badge-danger Expired
%span.badge.badge-danger
= s_('GitLabPages|Expired')
%div
= link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped"
= link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
= link_to s_('GitLabPages|Details'), project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped"
= link_to s_('GitLabPages|Remove'), project_pages_domain_path(@project, domain), data: { confirm: s_('GitLabPages|Are you sure?')}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
- if verification_enabled && domain.unverified?
%li.list-group-item.bs-callout-warning
#{domain.domain} is not verified. To learn how to verify ownership, visit your
#{link_to 'domain details', project_pages_domain_path(@project, domain)}.
- details_link_start = "<a href='#{project_pages_domain_path(@project, domain)}'>".html_safe
- details_link_end = '</a>'.html_safe
= s_('GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}.').html_safe % { domain: domain.domain,
link_start: details_link_start,
link_end: details_link_end }
- if can?(current_user, :update_pages, @project)
.card
.card-header
Domains
= s_('GitLabPages|Domains')
.nothing-here-block
Support for domains and certificates is disabled.
Ask your system's administrator to enable it.
= s_("GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it.")
- unless @project.pages_deployed?
.card.border-info
.card-header.bg-info.text-white
Configure pages
= s_('GitLabPages|Configure pages')
.card-body
%p
Learn how to upload your static site and have it served by
GitLab by following the
= succeed '.' do
= link_to 'documentation on GitLab Pages', help_page_path('user/project/pages/index.md'), target: '_blank'
- link_start = "<a href='#{help_page_path('user/project/pages/index.md')}' target='_blank' rel='noopener noreferrer'>".html_safe
- link_end = '</a>'.html_safe
= s_('GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}.').html_safe % { link_start: link_start,
link_end: link_end }
- page_title 'Pages'
%h3.page-title.with-button
Pages
= s_('GitLabPages|Pages')
- if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
= link_to new_project_pages_domain_path(@project), class: 'btn btn-success float-right', title: 'New Domain' do
New Domain
= link_to new_project_pages_domain_path(@project), class: 'btn btn-success float-right', title: s_('GitLabPages|New Domain') do
= s_('GitLabPages|New Domain')
%p.light
With GitLab Pages you can host your static websites on GitLab.
Combined with the power of GitLab CI and the help of GitLab Runner
you can deploy static pages for your individual projects, your user or your group.
= s_('GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group.')
- if Gitlab.config.pages.external_https
= render 'https_only'
......
---
title: Fix Gitaly SearchBlobs flag RPC injection
merge_request:
author:
type: security
---
title: Add support for the association of multiple milestones to the Releases page
merge_request: 17091
author:
type: changed
---
title: Fix Gitaly N+1 queries in related merge requests API
merge_request: 17850
author:
type: performance
......@@ -22,6 +22,8 @@ class ScheduleProjectAnyApprovalRuleMigration < ActiveRecord::Migration[5.2]
end
def up
return unless Gitlab.ee?
add_concurrent_index :projects, :id,
name: 'tmp_projects_with_approvals_before_merge',
where: 'approvals_before_merge <> 0'
......
......@@ -22,6 +22,8 @@ class ScheduleMergeRequestAnyApprovalRuleMigration < ActiveRecord::Migration[5.2
end
def up
return unless Gitlab.ee?
add_concurrent_index :merge_requests, :id,
name: 'tmp_merge_requests_with_approvals_before_merge',
where: 'approvals_before_merge <> 0'
......
......@@ -1370,8 +1370,11 @@ Example response:
"state": "opened",
"created_at": "2018-09-18T14:36:15.510Z",
"updated_at": "2018-09-19T07:45:13.089Z",
"closed_by": null,
"closed_at": null,
"target_branch": "v2.x",
"source_branch": "so_long_jquery",
"user_notes_count": 9,
"upvotes": 0,
"downvotes": 0,
"author": {
......@@ -1411,10 +1414,10 @@ Example response:
"merge_status": "cannot_be_merged",
"sha": "3b7b528e9353295c1c125dad281ac5b5deae5f12",
"merge_commit_sha": null,
"user_notes_count": 9,
"discussion_locked": null,
"should_remove_source_branch": null,
"force_remove_source_branch": false,
"reference": "!11",
"web_url": "https://gitlab.example.com/twitter/flight/merge_requests/4",
"time_stats": {
"time_estimate": 0,
......@@ -1422,7 +1425,67 @@ Example response:
"human_time_estimate": null,
"human_total_time_spent": null
},
"squash": false
"squash": false,
"task_completion_status": {
"count": 0,
"completed_count": 0
},
"changes_count": "10",
"latest_build_started_at": "2018-12-05T01:16:41.723Z",
"latest_build_finished_at": "2018-12-05T02:35:54.046Z",
"first_deployed_to_production_at": null,
"pipeline": {
"id": 38980952,
"sha": "81c6a84c7aebd45a1ac2c654aa87f11e32338e0a",
"ref": "test-branch",
"status": "success",
"web_url": "https://gitlab.com/gitlab-org/gitlab/pipelines/38980952"
},
"head_pipeline": {
"id": 38980952,
"sha": "81c6a84c7aebd45a1ac2c654aa87f11e32338e0a",
"ref": "test-branch",
"status": "success",
"web_url": "https://gitlab.example.com/twitter/flight/pipelines/38980952",
"before_sha": "3c738a37eb23cf4c0ed0d45d6ddde8aad4a8da51",
"tag": false,
"yaml_errors": null,
"user": {
"id": 19,
"name": "Jody Baumbach",
"username": "felipa.kuvalis",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/6541fc75fc4e87e203529bd275fafd07?s=80&d=identicon",
"web_url": "https://gitlab.example.com/felipa.kuvalis"
},
"created_at": "2018-12-05T01:16:13.342Z",
"updated_at": "2018-12-05T02:35:54.086Z",
"started_at": "2018-12-05T01:16:41.723Z",
"finished_at": "2018-12-05T02:35:54.046Z",
"committed_at": null,
"duration": 4436,
"coverage": "46.68",
"detailed_status": {
"icon": "status_warning",
"text": "passed",
"label": "passed with warnings",
"group": "success-with-warnings",
"tooltip": "passed",
"has_details": true,
"details_path": "/twitter/flight/pipelines/38",
"illustration": null,
"favicon": "https://gitlab.example.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
}
},
"diff_refs": {
"base_sha": "d052d768f0126e8cddf80afd8b1eb07f406a3fcb",
"head_sha": "81c6a84c7aebd45a1ac2c654aa87f11e32338e0a",
"start_sha": "d052d768f0126e8cddf80afd8b1eb07f406a3fcb"
},
"merge_error": null,
"user": {
"can_merge": true
}
}
]
```
......
......@@ -24,6 +24,11 @@ Parameters:
|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
| `filter` | string | yes | A [filter](#available-filters) expression. |
| `group_path` | string | yes | Full path to the group. |
| `startIndex` | integer | no | The 1-based index indicating where to start returning results from. A value of less than one will be interpreted as 1. |
| `count` | integer | no | Desired maximum number of query results. |
NOTE: **Note:**
Pagination follows the [SCIM spec](https://tools.ietf.org/html/rfc7644#section-3.4.2.4) rather than GitLab pagination as used elsewhere. If records change between requests it is possible for a page to either be missing records that have moved to a different page or repeat records from a previous request.
Example request:
......
......@@ -771,7 +771,7 @@ module API
end
class MergeRequest < MergeRequestBasic
expose :subscribed do |merge_request, options|
expose :subscribed, if: -> (_, options) { options.fetch(:include_subscribed, true) } do |merge_request, options|
merge_request.subscribed?(options[:current_user], options[:project])
end
......
......@@ -343,7 +343,8 @@ module API
present paginate(::Kaminari.paginate_array(merge_requests)),
with: Entities::MergeRequest,
current_user: current_user,
project: user_project
project: user_project,
include_subscribed: false
end
desc 'List merge requests closing issue' do
......
......@@ -2328,9 +2328,6 @@ msgstr ""
msgid "BillingPlans|Your GitLab.com trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}."
msgstr ""
msgid "BillingPlans|Your Gold trial will <strong>expire after %{expiration_date}</strong>. You can learn more about GitLab.com Gold by reading about our %{features_link}."
msgstr ""
msgid "BillingPlans|features"
msgstr ""
......@@ -7562,6 +7559,75 @@ msgstr ""
msgid "GitLab.com import"
msgstr ""
msgid "GitLabPages|%{domain} is not verified. To learn how to verify ownership, visit your %{link_start}domain details%{link_end}."
msgstr ""
msgid "GitLabPages|Access pages"
msgstr ""
msgid "GitLabPages|Are you sure?"
msgstr ""
msgid "GitLabPages|Certificate: %{subject}"
msgstr ""
msgid "GitLabPages|Configure pages"
msgstr ""
msgid "GitLabPages|Details"
msgstr ""
msgid "GitLabPages|Domains"
msgstr ""
msgid "GitLabPages|Expired"
msgstr ""
msgid "GitLabPages|Force HTTPS (requires valid certificates)"
msgstr ""
msgid "GitLabPages|It may take up to 30 minutes before the site is available after the first deployment."
msgstr ""
msgid "GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}."
msgstr ""
msgid "GitLabPages|New Domain"
msgstr ""
msgid "GitLabPages|Only project maintainers can remove pages"
msgstr ""
msgid "GitLabPages|Pages"
msgstr ""
msgid "GitLabPages|Remove"
msgstr ""
msgid "GitLabPages|Remove pages"
msgstr ""
msgid "GitLabPages|Removing pages will prevent them from being exposed to the outside world."
msgstr ""
msgid "GitLabPages|Save"
msgstr ""
msgid "GitLabPages|Support for domains and certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
msgid "GitLabPages|Unverified"
msgstr ""
msgid "GitLabPages|Verified"
msgstr ""
msgid "GitLabPages|With GitLab Pages you can host your static websites on GitLab. Combined with the power of GitLab CI and the help of GitLab Runner you can deploy static pages for your individual projects, your user or your group."
msgstr ""
msgid "GitLabPages|Your pages are served under:"
msgstr ""
msgid "Gitaly"
msgstr ""
......@@ -8751,9 +8817,6 @@ msgstr ""
msgid "IssuesAnalytics|Total:"
msgstr ""
msgid "It may take up to 30 minutes before the site is available after the first deployment."
msgstr ""
msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
msgstr ""
......@@ -9993,7 +10056,9 @@ msgid "Migration successful."
msgstr ""
msgid "Milestone"
msgstr ""
msgid_plural "Milestones"
msgstr[0] ""
msgstr[1] ""
msgid "Milestone lists not available with your current license"
msgstr ""
......@@ -18478,9 +18543,6 @@ msgstr ""
msgid "Your new personal access token has been created."
msgstr ""
msgid "Your pages are served under:"
msgstr ""
msgid "Your password reset token has expired."
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import MilestoneList from '~/releases/components/milestone_list.vue';
import Icon from '~/vue_shared/components/icon.vue';
import _ from 'underscore';
import { milestones } from '../mock_data';
describe('Milestone list', () => {
let wrapper;
const factory = milestonesProp => {
wrapper = shallowMount(MilestoneList, {
propsData: {
milestones: milestonesProp,
},
sync: false,
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders the milestone icon', () => {
factory(milestones);
expect(wrapper.find(Icon).exists()).toBe(true);
});
it('renders the label as "Milestone" if only a single milestone is passed in', () => {
factory(milestones.slice(0, 1));
expect(wrapper.find('.js-label-text').text()).toEqual('Milestone');
});
it('renders the label as "Milestones" if more than one milestone is passed in', () => {
factory(milestones);
expect(wrapper.find('.js-label-text').text()).toEqual('Milestones');
});
it('renders a link to the milestone with a tooltip', () => {
const milestone = _.first(milestones);
factory([milestone]);
const milestoneLink = wrapper.find(GlLink);
expect(milestoneLink.exists()).toBe(true);
expect(milestoneLink.text()).toBe(milestone.title);
expect(milestoneLink.attributes('href')).toBe(milestone.web_url);
expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description);
});
});
......@@ -3,6 +3,7 @@ import ReleaseBlock from '~/releases/components/release_block.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { first } from 'underscore';
import { release } from '../mock_data';
import Icon from '~/vue_shared/components/icon.vue';
describe('Release block', () => {
let wrapper;
......@@ -15,7 +16,7 @@ describe('Release block', () => {
});
};
const milestoneListExists = () => wrapper.find('.js-milestone-list').exists();
const milestoneListLabel = () => wrapper.find('.js-milestone-list-label');
afterEach(() => {
wrapper.destroy();
......@@ -98,20 +99,56 @@ describe('Release block', () => {
});
});
it('renders the milestone list if at least one milestone is associated to the release', () => {
factory(release);
it('renders the milestone icon', () => {
expect(
milestoneListLabel()
.find(Icon)
.exists(),
).toBe(true);
});
it('renders the label as "Milestones" if more than one milestone is passed in', () => {
expect(
milestoneListLabel()
.find('.js-label-text')
.text(),
).toEqual('Milestones');
});
it('renders a link to the milestone with a tooltip', () => {
const milestone = first(release.milestones);
const milestoneLink = wrapper.find('.js-milestone-link');
expect(milestoneLink.exists()).toBe(true);
expect(milestoneLink.text()).toBe(milestone.title);
expect(milestoneListExists()).toBe(true);
expect(milestoneLink.attributes('href')).toBe(milestone.web_url);
expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description);
});
});
it('does not render the milestone list if no milestones are associated to the release', () => {
const releaseClone = JSON.parse(JSON.stringify(release));
delete releaseClone.milestone;
delete releaseClone.milestones;
factory(releaseClone);
expect(milestoneListLabel().exists()).toBe(false);
});
it('renders the label as "Milestone" if only a single milestone is passed in', () => {
const releaseClone = JSON.parse(JSON.stringify(release));
releaseClone.milestones = releaseClone.milestones.slice(0, 1);
factory(releaseClone);
expect(milestoneListExists()).toBe(false);
expect(
milestoneListLabel()
.find('.js-label-text')
.text(),
).toEqual('Milestone');
});
it('renders upcoming release badge', () => {
......
......@@ -57,7 +57,7 @@ export const release = {
committed_date: '2019-08-26T17:47:07.000Z',
},
upcoming_release: false,
milestone: milestones[0],
milestones,
assets: {
count: 5,
sources: [
......
......@@ -736,6 +736,8 @@ describe API::Issues do
get_related_merge_requests(project.id, issue.iid)
expect_paginated_array_response(related_mr.id)
expect(response).to have_gitlab_http_status(200)
expect(json_response.last).not_to have_key('subscribed')
end
it 'renders 404 if project is not visible' 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