Commit 1e2dd2ac authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] Merge branch 'master' into 3776-ci-view-for-sast

* master: (73 commits)
  Fixes broken modal in DAST report
  Use more specific avatar_icon_for_user in EE.
  Extract repeated logic into #avatar_icon_for.
  Add changelog.
  Always eagerly load a note's author.
  Always eagerly load an issue's author.
  Extract method to improve readability.
  Remove generic #avatar_icon helper.
  Explicit use of avatar_icon_* calls depending on situation.
  Use avatar_icon_for_email for commits.
  Retrieve project creator's avatar by creator, not creator#email.
  Retrieve namespace owner's avatar by owner, not owner#email.
  Retrieve issue author's avatar by user, not user#email.
  Use more specific #avatar_icon_for_email.
  Use more specific #avatar_icon_for_user.
  Refactor and split ApplicationHelper#avatar_icon.
  EE port of pipeline-schedule-webpack
  Only check LFS integrity for first branch in push
  Improve validation message and add changelog
  Refactor weight select
  ...
parents fff8071a 46a437fd
...@@ -10,7 +10,6 @@ import SearchAutocomplete from './search_autocomplete'; ...@@ -10,7 +10,6 @@ import SearchAutocomplete from './search_autocomplete';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import UserCallout from './user_callout'; import UserCallout from './user_callout';
import ZenMode from './zen_mode'; import ZenMode from './zen_mode';
import initCompareAutocomplete from './compare_autocomplete';
import initGeoInfoModal from 'ee/init_geo_info_modal'; // eslint-disable-line import/first import initGeoInfoModal from 'ee/init_geo_info_modal'; // eslint-disable-line import/first
import initGroupAnalytics from 'ee/init_group_analytics'; // eslint-disable-line import/first import initGroupAnalytics from 'ee/init_group_analytics'; // eslint-disable-line import/first
import initPathLocks from 'ee/path_locks'; // eslint-disable-line import/first import initPathLocks from 'ee/path_locks'; // eslint-disable-line import/first
...@@ -53,19 +52,6 @@ var Dispatcher; ...@@ -53,19 +52,6 @@ var Dispatcher;
}); });
}); });
function initBlobEE() {
const dataEl = document.getElementById('js-file-lock');
if (dataEl) {
const {
toggle_path,
path,
} = JSON.parse(dataEl.innerHTML);
initPathLocks(toggle_path, path);
}
}
switch (page) { switch (page) {
case 'projects:environments:metrics': case 'projects:environments:metrics':
import('./pages/projects/environments/metrics') import('./pages/projects/environments/metrics')
...@@ -86,7 +72,6 @@ var Dispatcher; ...@@ -86,7 +72,6 @@ var Dispatcher;
import('./pages/projects/milestones/show') import('./pages/projects/milestones/show')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
new UserCallout();
break; break;
case 'groups:milestones:show': case 'groups:milestones:show':
import('./pages/groups/milestones/show') import('./pages/groups/milestones/show')
...@@ -185,14 +170,6 @@ var Dispatcher; ...@@ -185,14 +170,6 @@ var Dispatcher;
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'groups:epics:show':
new ZenMode();
break;
case 'groups:epics:index':
import(/* webpackChunkName: "ee_epics_show" */ 'ee/pages/epics')
.then(callDefault)
.catch(fail);
break;
case 'projects:compare:show': case 'projects:compare:show':
import('./pages/projects/compare/show') import('./pages/projects/compare/show')
.then(callDefault) .then(callDefault)
...@@ -229,24 +206,17 @@ var Dispatcher; ...@@ -229,24 +206,17 @@ var Dispatcher;
import('./pages/projects/merge_requests/creations/new') import('./pages/projects/merge_requests/creations/new')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
new UserCallout();
case 'projects:merge_requests:creations:diffs': case 'projects:merge_requests:creations:diffs':
import('./pages/projects/merge_requests/creations/diffs') import('./pages/projects/merge_requests/creations/diffs')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
shortcut_handler = true; shortcut_handler = true;
// ee-start
initApprovals();
// ee-end
break; break;
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
import('./pages/projects/merge_requests/edit') import('./pages/projects/merge_requests/edit')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
shortcut_handler = true; shortcut_handler = true;
// ee-start
initApprovals();
// ee-end
break; break;
case 'projects:tags:new': case 'projects:tags:new':
import('./pages/projects/tags/new') import('./pages/projects/tags/new')
...@@ -331,9 +301,6 @@ var Dispatcher; ...@@ -331,9 +301,6 @@ var Dispatcher;
break; break;
case 'projects:show': case 'projects:show':
shortcut_handler = true; shortcut_handler = true;
// ee-start
initGeoInfoModal();
// ee-end
break; break;
case 'projects:edit': case 'projects:edit':
import(/* webpackChunkName: "ee_projects_edit" */ 'ee/pages/projects/edit') import(/* webpackChunkName: "ee_projects_edit" */ 'ee/pages/projects/edit')
...@@ -415,14 +382,12 @@ var Dispatcher; ...@@ -415,14 +382,12 @@ var Dispatcher;
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
shortcut_handler = true; shortcut_handler = true;
initBlobEE();
break; break;
case 'projects:blame:show': case 'projects:blame:show':
import('./pages/projects/blame/show') import('./pages/projects/blame/show')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
shortcut_handler = true; shortcut_handler = true;
initBlobEE();
break; break;
case 'groups:labels:new': case 'groups:labels:new':
import('./pages/groups/labels/new') import('./pages/groups/labels/new')
...@@ -485,26 +450,11 @@ var Dispatcher; ...@@ -485,26 +450,11 @@ var Dispatcher;
import('./pages/search/show') import('./pages/search/show')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
new UserCallout();
break;
case 'projects:mirrors:show':
case 'projects:mirrors:update':
new UsersSelect();
break;
case 'admin:emails:show':
import(/* webpackChunkName: "ee_admin_emails_show" */ 'ee/pages/admin/emails/show').then(m => m.default()).catch(fail);
break;
case 'admin:audit_logs:index':
import(/* webpackChunkName: "ee_audit_logs" */ 'ee/pages/admin/audit_logs').then(m => m.default()).catch(fail);
break; break;
case 'projects:settings:repository:show': case 'projects:settings:repository:show':
import('./pages/projects/settings/repository/show') import('./pages/projects/settings/repository/show')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
// ee-start
new UsersSelect();
new UserCallout();
// ee-end
break; break;
case 'projects:settings:ci_cd:show': case 'projects:settings:ci_cd:show':
import('./pages/projects/settings/ci_cd/show') import('./pages/projects/settings/ci_cd/show')
...@@ -552,11 +502,6 @@ var Dispatcher; ...@@ -552,11 +502,6 @@ var Dispatcher;
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'profiles:personal_access_tokens:index':
import('./pages/profiles/personal_access_tokens')
.then(callDefault)
.catch(fail);
break;
case 'projects:clusters:show': case 'projects:clusters:show':
case 'projects:clusters:update': case 'projects:clusters:update':
case 'projects:clusters:destroy': case 'projects:clusters:destroy':
...@@ -573,14 +518,6 @@ var Dispatcher; ...@@ -573,14 +518,6 @@ var Dispatcher;
import('./pages/dashboard/groups/index') import('./pages/dashboard/groups/index')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
case 'admin:licenses:new':
import(/* webpackChunkName: "admin_licenses" */ 'ee/pages/admin/licenses/new').then(m => m.default()).catch(fail);
break;
case 'groups:analytics:show':
initGroupAnalytics();
break;
case 'groups:ldap_group_links:index':
initLDAPGroupsSelect();
break; break;
} }
switch (path[0]) { switch (path[0]) {
...@@ -616,11 +553,7 @@ var Dispatcher; ...@@ -616,11 +553,7 @@ var Dispatcher;
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'edit':
import(/* webpackChunkName: "ee_admin_groups_edit" */ 'ee/pages/admin/groups/edit').then(m => m.default()).catch(fail);
break;
} }
break; break;
case 'projects': case 'projects':
import('./pages/admin/projects') import('./pages/admin/projects')
...@@ -645,11 +578,6 @@ var Dispatcher; ...@@ -645,11 +578,6 @@ var Dispatcher;
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'geo_nodes':
import(/* webpackChunkName: 'geo_node_form' */ './geo/geo_node_form')
.then(geoNodeForm => geoNodeForm.default($('.js-geo-node-form')))
.catch(() => {});
break;
} }
break; break;
case 'profiles': case 'profiles':
...@@ -664,7 +592,9 @@ var Dispatcher; ...@@ -664,7 +592,9 @@ var Dispatcher;
shortcut_handler = true; shortcut_handler = true;
switch (path[1]) { switch (path[1]) {
case 'compare': case 'compare':
initCompareAutocomplete(); import('./pages/projects/compare')
.then(callDefault)
.catch(fail);
break; break;
case 'create': case 'create':
case 'new': case 'new':
...@@ -686,6 +616,81 @@ var Dispatcher; ...@@ -686,6 +616,81 @@ var Dispatcher;
new Shortcuts(); new Shortcuts();
} }
// EE-only route-based code
function initBlobEE() {
const dataEl = document.getElementById('js-file-lock');
if (dataEl) {
const {
toggle_path,
path,
} = JSON.parse(dataEl.innerHTML);
initPathLocks(toggle_path, path);
}
}
switch (page) {
case 'groups:epics:show':
new ZenMode();
break;
case 'groups:epics:index':
import(/* webpackChunkName: "ee_epics_index" */ 'ee/pages/epics')
.then(callDefault)
.catch(fail);
break;
case 'projects:milestones:show':
case 'search:show':
new UserCallout();
break;
case 'projects:merge_requests:creations:new':
new UserCallout();
initApprovals();
break;
case 'projects:merge_requests:creations:diffs':
case 'projects:merge_requests:edit':
initApprovals();
break;
case 'projects:show':
initGeoInfoModal();
break;
case 'projects:blob:show':
case 'projects:blame:show':
initBlobEE();
break;
case 'projects:mirrors:show':
case 'projects:mirrors:update':
new UsersSelect();
break;
case 'admin:emails:show':
import(/* webpackChunkName: "ee_admin_emails_show" */ 'ee/pages/admin/emails/show').then(m => m.default()).catch(fail);
break;
case 'admin:audit_logs:index':
import(/* webpackChunkName: "ee_audit_logs" */ 'ee/pages/admin/audit_logs').then(m => m.default()).catch(fail);
break;
case 'projects:settings:repository:show':
new UsersSelect();
new UserCallout();
break;
case 'admin:licenses:new':
import(/* webpackChunkName: "admin_licenses" */ 'ee/pages/admin/licenses/new').then(m => m.default()).catch(fail);
break;
case 'groups:analytics:show':
initGroupAnalytics();
break;
case 'groups:ldap_group_links:index':
initLDAPGroupsSelect();
break;
case 'admin:groups:edit':
import(/* webpackChunkName: "ee_admin_groups_edit" */ 'ee/pages/admin/groups/edit').then(m => m.default()).catch(fail);
break;
case 'admin:geo_nodes':
import(/* webpackChunkName: 'geo_node_form' */ './geo/geo_node_form')
.then(geoNodeForm => geoNodeForm.default($('.js-geo-node-form')))
.catch(() => {});
break;
}
if (document.querySelector('#peek')) { if (document.querySelector('#peek')) {
import('./performance_bar') import('./performance_bar')
.then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap .then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
......
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
import Vue from 'vue'; import Vue from 'vue';
import PipelineSchedulesCallout from './components/pipeline_schedules_callout.vue'; import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipeline-schedules-callout', el: '#pipeline-schedules-callout',
......
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Translate from '../../vue_shared/translate'; import Translate from '../../../../../vue_shared/translate';
import illustrationSvg from '../icons/intro_illustration.svg'; import illustrationSvg from '../icons/intro_illustration.svg';
Vue.use(Translate); Vue.use(Translate);
......
import Vue from 'vue'; import Vue from 'vue';
import Translate from '../vue_shared/translate'; import Translate from '../../../../vue_shared/translate';
import GlFieldErrors from '../gl_field_errors'; import GlFieldErrors from '../../../../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue'; import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown'; import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown'; import TargetBranchDropdown from './components/target_branch_dropdown';
import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list'; import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list';
Vue.use(Translate); Vue.use(Translate);
...@@ -27,7 +27,7 @@ function initIntervalPatternInput() { ...@@ -27,7 +27,7 @@ function initIntervalPatternInput() {
}); });
} }
document.addEventListener('DOMContentLoaded', () => { export default () => {
/* Most of the form is written in haml, but for fields with more complex behaviors, /* Most of the form is written in haml, but for fields with more complex behaviors,
* you should mount individual Vue components here. If at some point components need * you should mount individual Vue components here. If at some point components need
* to share state, it may make sense to refactor the whole form to Vue */ * to share state, it may make sense to refactor the whole form to Vue */
...@@ -46,4 +46,4 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -46,4 +46,4 @@ document.addEventListener('DOMContentLoaded', () => {
container: $('.js-ci-variable-list-section'), container: $('.js-ci-variable-list-section'),
formField: 'schedule', formField: 'schedule',
}); });
}); };
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetNotAllowed',
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="success" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Ready to be merged automatically.
Ask someone with write access to this repository to merge this request
</span>
</div>
</div>
`,
};
<script>
import StatusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetNotAllowed',
components: {
StatusIcon,
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="success"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
{{ s__(`mrWidget|Ready to be merged automatically.
Ask someone with write access to this repository to merge this request`) }}
</span>
</div>
</div>
</template>
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetPipelineBlocked',
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Pipeline blocked. The pipeline for this merge request requires a manual action to proceed
</span>
</div>
</div>
`,
};
<script>
import StatusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetPipelineBlocked',
components: {
StatusIcon,
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
{{ s__(`mrWidget|Pipeline blocked.
The pipeline for this merge request requires a manual action to proceed`) }}
</span>
</div>
</div>
</template>
...@@ -25,11 +25,11 @@ export { default as ArchivedState } from './components/states/mr_widget_archived ...@@ -25,11 +25,11 @@ export { default as ArchivedState } from './components/states/mr_widget_archived
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge'; export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch'; export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions'; export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue'; export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue'; export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
......
...@@ -805,10 +805,19 @@ ...@@ -805,10 +805,19 @@
margin: 0; margin: 0;
line-height: $code_line_height; line-height: $code_line_height;
.btn-open-modal {
padding: 0 5px 4px;
}
.mr-widget-code-quality-list-item { .mr-widget-code-quality-list-item {
display: flex; display: flex;
} }
.mr-widget-code-quality-list-item-modal {
display: flex;
flex-wrap: wrap;
}
.failed .mr-widget-code-quality-icon { .failed .mr-widget-code-quality-icon {
color: $red-500; color: $red-500;
} }
......
...@@ -21,7 +21,7 @@ class IssuesFinder < IssuableFinder ...@@ -21,7 +21,7 @@ class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
def klass def klass
Issue Issue.includes(:author)
end end
def with_confidentiality_access_check def with_confidentiality_access_check
......
...@@ -57,7 +57,7 @@ class NotesFinder ...@@ -57,7 +57,7 @@ class NotesFinder
types = %w(commit issue merge_request snippet) types = %w(commit issue merge_request snippet)
note_relations = types.map { |t| notes_for_type(t) } note_relations = types.map { |t| notes_for_type(t) }
note_relations.map! { |notes| search(notes) } note_relations.map! { |notes| search(notes) }
UnionFinder.new.find_union(note_relations, Note) UnionFinder.new.find_union(note_relations, Note.includes(:author))
end end
def noteables_for_type(noteable_type) def noteables_for_type(noteable_type)
......
...@@ -68,18 +68,32 @@ module ApplicationHelper ...@@ -68,18 +68,32 @@ module ApplicationHelper
end end
end end
def avatar_icon(user_or_email = nil, size = nil, scale = 2, only_path: true) # Takes both user and email and returns the avatar_icon by
user = # user (preferred) or email.
if user_or_email.is_a?(User) def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true)
user_or_email if user
else avatar_icon_for_user(user, size, scale, only_path: only_path)
User.find_by_any_email(user_or_email.try(:downcase)) elsif email
end avatar_icon_for_email(email, size, scale, only_path: only_path)
else
default_avatar
end
end
def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true)
user = User.find_by_any_email(email.try(:downcase))
if user
avatar_icon_for_user(user, size, scale, only_path: only_path)
else
gravatar_icon(email, size, scale)
end
end
def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true)
if user if user
user.avatar_url(size: size, only_path: only_path) || default_avatar user.avatar_url(size: size, only_path: only_path) || default_avatar
else else
gravatar_icon(user_or_email, size, scale) gravatar_icon(nil, size, scale)
end end
end end
......
...@@ -8,10 +8,22 @@ module AvatarsHelper ...@@ -8,10 +8,22 @@ module AvatarsHelper
})) }))
end end
def user_avatar_url_for(options = {})
if options[:url]
options[:url]
elsif options[:user]
avatar_icon_for_user(options[:user], options[:size])
else
avatar_icon_for_email(options[:user_email], options[:size])
end
end
def user_avatar_without_link(options = {}) def user_avatar_without_link(options = {})
avatar_size = options[:size] || 16 avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name] user_name = options[:user].try(:name) || options[:user_name]
avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size)
avatar_url = user_avatar_url_for(options.merge(size: avatar_size))
has_tooltip = options[:has_tooltip].nil? ? true : options[:has_tooltip] has_tooltip = options[:has_tooltip].nil? ? true : options[:has_tooltip]
data_attributes = options[:data] || {} data_attributes = options[:data] || {}
css_class = %W[avatar s#{avatar_size}].push(*options[:css_class]) css_class = %W[avatar s#{avatar_size}].push(*options[:css_class])
......
...@@ -35,7 +35,7 @@ module NamespacesHelper ...@@ -35,7 +35,7 @@ module NamespacesHelper
if namespace.is_a?(Group) if namespace.is_a?(Group)
group_icon(namespace) group_icon(namespace)
else else
avatar_icon(namespace.owner.email, size) avatar_icon_for_user(namespace.owner, size)
end end
end end
......
...@@ -21,7 +21,7 @@ module ProjectsHelper ...@@ -21,7 +21,7 @@ module ProjectsHelper
classes = %W[avatar avatar-inline s#{opts[:size]}] classes = %W[avatar avatar-inline s#{opts[:size]}]
classes << opts[:avatar_class] if opts[:avatar_class] classes << opts[:avatar_class] if opts[:avatar_class]
avatar = avatar_icon(author, opts[:size]) avatar = avatar_icon_for_user(author, opts[:size])
src = opts[:lazy_load] ? nil : avatar src = opts[:lazy_load] ? nil : avatar
image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar) image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
...@@ -298,6 +298,10 @@ module ProjectsHelper ...@@ -298,6 +298,10 @@ module ProjectsHelper
nav_tabs << :pipelines nav_tabs << :pipelines
end end
if project.external_issue_tracker
nav_tabs << :external_issue_tracker
end
tab_ability_map.each do |tab, ability| tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project) if can?(current_user, ability, project)
nav_tabs << tab nav_tabs << tab
......
...@@ -317,7 +317,7 @@ class Member < ActiveRecord::Base ...@@ -317,7 +317,7 @@ class Member < ActiveRecord::Base
end end
def notification_setting def notification_setting
@notification_setting ||= user.notification_settings_for(source) @notification_setting ||= user&.notification_settings_for(source)
end end
def notifiable?(type, opts = {}) def notifiable?(type, opts = {})
......
...@@ -266,7 +266,7 @@ class Project < ActiveRecord::Base ...@@ -266,7 +266,7 @@ class Project < ActiveRecord::Base
validates :repository_storage, validates :repository_storage,
presence: true, presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates :variables, variable_duplicates: true validates :variables, variable_duplicates: { scope: :environment_scope }
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......
...@@ -10,6 +10,8 @@ class JiraService < IssueTrackerService ...@@ -10,6 +10,8 @@ class JiraService < IssueTrackerService
before_update :reset_password before_update :reset_password
alias_method :project_url, :url
# This is confusing, but JiraService does not really support these events. # This is confusing, but JiraService does not really support these events.
# The values here are required to display correct options in the service # The values here are required to display correct options in the service
# configuration screen. # configuration screen.
......
...@@ -11,6 +11,7 @@ module Members ...@@ -11,6 +11,7 @@ module Members
Member.transaction do Member.transaction do
unassign_issues_and_merge_requests(member) unless member.invite? unassign_issues_and_merge_requests(member) unless member.invite?
member.notification_setting&.destroy
member.destroy member.destroy
end end
......
# VariableDuplicatesValidator # VariableDuplicatesValidator
# #
# This validtor is designed for especially the following condition # This validator is designed for especially the following condition
# - Use `accepts_nested_attributes_for :xxx` in a parent model # - Use `accepts_nested_attributes_for :xxx` in a parent model
# - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model # - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model
class VariableDuplicatesValidator < ActiveModel::EachValidator class VariableDuplicatesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
duplicates = value.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first) if options[:scope]
scoped = value.group_by do |variable|
Array(options[:scope]).map { |attr| variable.send(attr) } # rubocop:disable GitlabSecurity/PublicSend
end
scoped.each_value { |scope| validate_duplicates(record, attribute, scope) }
else
validate_duplicates(record, attribute, value)
end
end
private
def validate_duplicates(record, attribute, values)
duplicates = values.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
if duplicates.any? if duplicates.any?
record.errors.add(attribute, "Duplicate variables: #{duplicates.join(", ")}") error_message = "have duplicate values (#{duplicates.join(", ")})"
error_message += " for #{values.first.send(options[:scope])} scope" if options[:scope] # rubocop:disable GitlabSecurity/PublicSend
record.errors.add(attribute, error_message)
end end
end end
end end
%li.flex-row %li.flex-row
.user-avatar .user-avatar
= image_tag avatar_icon(user), class: "avatar", alt: '' = image_tag avatar_icon_for_user(user), class: "avatar", alt: ''
.row-main-content .row-main-content
.user-name.row-title.str-truncated-100 .user-name.row-title.str-truncated-100
= link_to user.name, [:admin, user] = link_to user.name, [:admin, user]
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= @user.name = @user.name
%ul.well-list %ul.well-list
%li %li
= image_tag avatar_icon(@user, 60), class: "avatar s60" = image_tag avatar_icon_for_user(@user, 60), class: "avatar s60"
%li %li
%span.light Profile page: %span.light Profile page:
%strong %strong
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
= link_to user_path(discussion.author) do = link_to user_path(discussion.author) do
= image_tag avatar_icon(discussion.author), class: "avatar s40" = image_tag avatar_icon_for_user(discussion.author), class: "avatar s40"
.timeline-content .timeline-content
.discussion.js-toggle-container{ data: { discussion_id: discussion.id } } .discussion.js-toggle-container{ data: { discussion_id: discussion.id } }
.discussion-header .discussion-header
......
...@@ -10,7 +10,7 @@ xml.entry do ...@@ -10,7 +10,7 @@ xml.entry do
# eager-loaded. This allows us to re-use the user object's Email address, # eager-loaded. This allows us to re-use the user object's Email address,
# instead of having to run additional queries to figure out what Email to use # instead of having to run additional queries to figure out what Email to use
# for the avatar. # for the avatar.
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author)) xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon_for_user(event.author))
xml.author do xml.author do
xml.username event.author_username xml.username event.author_username
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
.example .example
.cover-block .cover-block
.avatar-holder .avatar-holder
= image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: '' = image_tag avatar_icon_for_email('admin@example.com', 90), class: "avatar s90", alt: ''
.cover-title .cover-title
John Smith John Smith
......
...@@ -3,7 +3,7 @@ xml.entry do ...@@ -3,7 +3,7 @@ xml.entry do
xml.link href: project_issue_url(issue.project, issue) xml.link href: project_issue_url(issue.project, issue)
xml.title truncate(issue.title, length: 80) xml.title truncate(issue.title, length: 80)
xml.updated issue.updated_at.xmlschema xml.updated issue.updated_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon_for_user(issue.author))
xml.author do xml.author do
xml.name issue.author_name xml.name issue.author_name
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
= todos_count_format(todos_pending_count) = todos_count_format(todos_pending_count)
%li.header-user.dropdown %li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
= sprite_icon('angle-down', css_class: 'caret-down') = sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right .dropdown-menu-nav.dropdown-menu-align-right
%ul %ul
......
...@@ -141,6 +141,19 @@ ...@@ -141,6 +141,19 @@
= link_to project_milestones_path(@project), title: 'Milestones' do = link_to project_milestones_path(@project), title: 'Milestones' do
%span %span
Milestones Milestones
- if project_nav_tab? :external_issue_tracker
= nav_link do
- issue_tracker = @project.external_issue_tracker
= link_to issue_tracker.issue_tracker_path, class: 'shortcuts-external_tracker' do
.nav-icon-container
= sprite_icon('issue-external')
%span.nav-item-name
= issue_tracker.title
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(html_options: { class: "fly-out-top-item" } ) do
= link_to issue_tracker.issue_tracker_path do
%strong.fly-out-top-item-name
= issue_tracker.title
- if project_nav_tab? :merge_requests - if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %img.avatar{ height: "24", src: avatar_icon_for(commit.author, commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.author - if commit.author
%a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %img.avatar{ height: "24", src: avatar_icon_for(commit.committer, commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.committer - if commit.committer
%a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" }
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
triggered by triggered by
- if @pipeline.user - if @pipeline.user
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" }
%img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
%a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" }
= @pipeline.user.name = @pipeline.user.name
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %img.avatar{ height: "24", src: avatar_icon_for(commit.author, commit.author_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.author - if commit.author
%a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" }
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(commit.committer || commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %img.avatar{ height: "24", src: avatar_icon_for(commit.committer, commit.committer_email, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
- if commit.committer - if commit.committer
%a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(commit.committer), style: "color:#333333;text-decoration:none;" }
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
triggered by triggered by
- if @pipeline.user - if @pipeline.user
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;padding-left:5px", width: "24" }
%img.avatar{ height: "24", src: avatar_icon(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@pipeline.user, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;font-weight:500;line-height:1.4;vertical-align:baseline;" }
%a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@pipeline.user), style: "color:#333333;text-decoration:none;" }
= @pipeline.user.name = @pipeline.user.name
......
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
or change it at #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host} or change it at #{link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host}
.col-lg-8 .col-lg-8
.clearfix.avatar-image.append-bottom-default .clearfix.avatar-image.append-bottom-default
= link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' = image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160'
%h5.prepend-top-0= _("Upload new avatar") %h5.prepend-top-0= _("Upload new avatar")
.prepend-top-5.append-bottom-10 .prepend-top-5.append-bottom-10
%button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...") %button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...")
......
...@@ -3,7 +3,7 @@ xml.entry do ...@@ -3,7 +3,7 @@ xml.entry do
xml.link href: project_commit_url(@project, id: commit.id) xml.link href: project_commit_url(@project, id: commit.id)
xml.title truncate(commit.title, length: 80, escape: false) xml.title truncate(commit.title, length: 80, escape: false)
xml.updated commit.committed_date.xmlschema xml.updated commit.committed_date.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email)) xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon_for_email(commit.author_email))
xml.author do |author| xml.author do |author|
xml.name commit.author_name xml.name commit.author_name
......
by by
%a{ href: user_path(@build.user) } %a{ href: user_path(@build.user) }
%span.hidden-xs %span.hidden-xs
= image_tag avatar_icon(@build.user, 24), class: "avatar s24" = image_tag avatar_icon_for_user(@build.user, 24), class: "avatar s24"
%strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } }
= @build.user.name = @build.user.name
%strong.visible-xs-inline= @build.user.to_reference %strong.visible-xs-inline= @build.user.to_reference
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
author: { author: {
name: c.author_name, name: c.author_name,
email: c.author_email, email: c.author_email,
icon: image_path(avatar_icon(c.author_email, 20)) icon: image_path(avatar_icon_for_email(c.author_email, 20))
}, },
time: c.time, time: c.time,
space: c.spaces.first, space: c.spaces.first,
......
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'schedule_form'
= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f|
= form_errors(@schedule) = form_errors(@schedule)
.form-group .form-group
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
= s_("PipelineSchedules|Inactive") = s_("PipelineSchedules|Inactive")
%td %td
- if pipeline_schedule.owner - if pipeline_schedule.owner
= image_tag avatar_icon(pipeline_schedule.owner, 20), class: "avatar s20" = image_tag avatar_icon_for_user(pipeline_schedule.owner, 20), class: "avatar s20"
= link_to user_path(pipeline_schedule.owner) do = link_to user_path(pipeline_schedule.owner) do
= pipeline_schedule.owner&.name = pipeline_schedule.owner&.name
%td %td
......
- breadcrumb_title _("Schedules") - breadcrumb_title _("Schedules")
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'schedules_index'
- @no_container = true - @no_container = true
- page_title _("Pipeline Schedules") - page_title _("Pipeline Schedules")
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
%div %div
= link_to project_commits_path(@project, commit.id) do = link_to project_commits_path(@project, commit.id) do
%code= commit.short_id %code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: '' = image_tag avatar_icon_for_email(commit.author_email), class: "", width: 16, alt: ''
= markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author) = markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author)
%td %td
%span.pull-right.cgray %span.pull-right.cgray
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= snippet.title = snippet.title
by by
= link_to user_snippets_path(snippet.author) do = link_to user_snippets_path(snippet.author) do
= image_tag avatar_icon(snippet.author), class: "avatar avatar-inline s16", alt: '' = image_tag avatar_icon_for_user(snippet.author), class: "avatar avatar-inline s16", alt: ''
= snippet.author_name = snippet.author_name
%span.light= time_ago_with_tooltip(snippet.created_at) %span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title %h4.snippet-title
......
...@@ -18,6 +18,6 @@ ...@@ -18,6 +18,6 @@
%span %span
by by
= link_to user_snippets_path(snippet_title.author) do = link_to user_snippets_path(snippet_title.author) do
= image_tag avatar_icon(snippet_title.author), class: "avatar avatar-inline s16", alt: '' = image_tag avatar_icon_for_user(snippet_title.author), class: "avatar avatar-inline s16", alt: ''
= snippet_title.author_name = snippet_title.author_name
%span.light= time_ago_with_tooltip(snippet_title.created_at) %span.light= time_ago_with_tooltip(snippet_title.created_at)
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%li.member{ class: [dom_class(member), ("is-overriden" if member.override)], id: dom_id(member) } %li.member{ class: [dom_class(member), ("is-overriden" if member.override)], id: dom_id(member) }
%span.list-item-name %span.list-item-name
- if user - if user
= image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' = image_tag avatar_icon_for_user(user, 40), class: "avatar s40", alt: ''
.user-info .user-info
= link_to user.name, user_path(user), class: 'member' = link_to user.name, user_path(user), class: 'member'
%span.cgray= user.to_reference %span.cgray= user.to_reference
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
Expires in #{distance_of_time_in_words_to_now(member.expires_at)} Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
- else - else
= image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: '' = image_tag avatar_icon_for_email(member.invite_email, 40), class: "avatar s40", alt: ''
.user-info .user-info
.member= member.invite_email .member= member.invite_email
.cgray .cgray
......
...@@ -28,4 +28,4 @@ ...@@ -28,4 +28,4 @@
- assignees.each do |assignee| - assignees.each do |assignee|
= link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }), = link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }),
class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- image_tag(avatar_icon(assignee, 16), class: "avatar s16", alt: '') - image_tag(avatar_icon_for_user(assignee, 16), class: "avatar s16", alt: '')
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- users.each do |user| - users.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32" = image_tag avatar_icon_for_user(user, 32), class: "avatar s32"
%strong= truncate(user.name, length: 40) %strong= truncate(user.name, length: 40)
%div %div
%small.cgray= user.username %small.cgray= user.username
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
= icon_for_system_note(note) = icon_for_system_note(note)
- else - else
%a.image-diff-avatar-link{ href: user_path(note.author) } %a.image-diff-avatar-link{ href: user_path(note.author) }
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' = image_tag avatar_icon_for_user(note.author), alt: '', class: 'avatar s40'
- if note.is_a?(DiffNote) && note.on_image? - if note.is_a?(DiffNote) && note.on_image?
- if show_image_comment_badge && note_counter == 0 - if show_image_comment_badge && note_counter == 0
-# Only show this for the first comment in the discussion -# Only show this for the first comment in the discussion
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.timeline-icon.hidden-xs.hidden-sm .timeline-icon.hidden-xs.hidden-sm
%a.author_link{ href: user_path(current_user) } %a.author_link{ href: user_path(current_user) }
= image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40' = image_tag avatar_icon_for_user(current_user), alt: current_user.to_reference, class: 'avatar s40'
.timeline-content.timeline-content-form .timeline-content.timeline-content-form
= render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete = render "shared/notes/form", view: diff_view, supports_autocomplete: autocomplete
- elsif !current_user - elsif !current_user
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
.avatar-container.s40 .avatar-container.s40
= link_to project_path(project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar - if project.creator && use_creator_avatar
= image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:'' = image_tag avatar_icon_for_user(project.creator, 40), class: "avatar s40", alt:''
- else - else
= project_icon(project, alt: '', class: 'avatar project-avatar s40') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
.project-details .project-details
......
- link_project = local_assigns.fetch(:link_project, false) - link_project = local_assigns.fetch(:link_project, false)
%li.snippet-row %li.snippet-row
= image_tag avatar_icon(snippet.author), class: "avatar s40 hidden-xs", alt: '' = image_tag avatar_icon_for_user(snippet.author), class: "avatar s40 hidden-xs", alt: ''
.title .title
= link_to reliable_snippet_path(snippet) do = link_to reliable_snippet_path(snippet) do
......
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
.profile-header .profile-header
.avatar-holder .avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' = image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: ''
.user-info .user-info
.cover-title .cover-title
......
---
title: "[Geo] Fix redownload repository recovery when there is not local repo at all"
merge_request:
author:
type: fixed
---
title: Fix broken CSS in modal for DAST report
merge_request:
author:
type: fixed
---
title: Fix validation of environment scope of variables
merge_request:
author:
type: fixed
---
title: Display a link to external issue tracker when enabled
merge_request:
author:
type: changed
---
title: Remove user notification settings for groups and projects when user leaves
merge_request: 16906
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Use a user object in ApplicationHelper#avatar_icon where possible to avoid
N+1 queries.
merge_request: 42800
author:
type: performance
---
title: 'API: Get references a commit is pushed to'
merge_request: 15026
author: Robert Schilling
type: added
---
title: Only check LFS integrity for first ref in a push to avoid timeout
merge_request: 17098
author:
type: performance
...@@ -105,8 +105,6 @@ var config = { ...@@ -105,8 +105,6 @@ var config = {
ide: './ide/index.js', ide: './ide/index.js',
sidebar: './sidebar/sidebar_bundle.js', sidebar: './sidebar/sidebar_bundle.js',
ee_sidebar: 'ee/sidebar/sidebar_bundle.js', ee_sidebar: 'ee/sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
schedules_index: './pipeline_schedules/pipeline_schedules_index_bundle.js',
snippet: './snippet/snippet_bundle.js', snippet: './snippet/snippet_bundle.js',
sketch_viewer: './blob/sketch_viewer.js', sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js', stl_viewer: './blob/stl_viewer.js',
...@@ -173,7 +171,7 @@ var config = { ...@@ -173,7 +171,7 @@ var config = {
include: /node_modules\/katex\/dist/, include: /node_modules\/katex\/dist/,
use: [ use: [
{ loader: 'style-loader' }, { loader: 'style-loader' },
{ {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
name: '[name].[hash].[ext]' name: '[name].[hash].[ext]'
......
...@@ -198,6 +198,41 @@ Example response: ...@@ -198,6 +198,41 @@ Example response:
} }
``` ```
## Get references a commit is pushed to
> [Introduced][ce-15026] in GitLab 10.6
Get all references (from branches or tags) a commit is pushed to.
The pagination parameters `page` and `per_page` can be used to restrict the list of references.
```
GET /projects/:id/repository/commits/:sha/refs
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
| `sha` | string | yes | The commit hash |
| `type` | string | no | The scope of commits. Possible values `branch`, `tag`, `all`. Default is `all`. |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits/5937ac0a7beb003549fc5fd26fc247adbce4a52e/refs?type=all"
```
Example response:
```json
[
{"type": "branch", "name": "'test'"},
{"type": "branch", "name": "add-balsamiq-file"},
{"type": "branch", "name": "wip"},
{"type": "tag", "name": "v1.1.0"}
]
```
## Cherry pick a commit ## Cherry pick a commit
> [Introduced][ce-8047] in GitLab 8.15. > [Introduced][ce-8047] in GitLab 8.15.
...@@ -500,3 +535,4 @@ Example response: ...@@ -500,3 +535,4 @@ Example response:
[ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit" [ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit"
[ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047 [ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047
[ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026
...@@ -39,7 +39,7 @@ Example response: ...@@ -39,7 +39,7 @@ Example response:
"path": "group1", "path": "group1",
"kind": "group" "kind": "group"
"full_path": "group1", "full_path": "group1",
"parent_id": "null", "parent_id": null,
"members_count_with_descendants": 2, "members_count_with_descendants": 2,
"plan": "bronze" "plan": "bronze"
}, },
...@@ -49,7 +49,7 @@ Example response: ...@@ -49,7 +49,7 @@ Example response:
"path": "bar", "path": "bar",
"kind": "group", "kind": "group",
"full_path": "foo/bar", "full_path": "foo/bar",
"parent_id": "9", "parent_id": 9,
"members_count_with_descendants": 5 "members_count_with_descendants": 5
} }
] ]
...@@ -85,7 +85,7 @@ Example response: ...@@ -85,7 +85,7 @@ Example response:
"path": "twitter", "path": "twitter",
"kind": "group", "kind": "group",
"full_path": "twitter", "full_path": "twitter",
"parent_id": "null", "parent_id": null,
"members_count_with_descendants": 2 "members_count_with_descendants": 2
} }
] ]
...@@ -118,7 +118,7 @@ Example response: ...@@ -118,7 +118,7 @@ Example response:
"path": "group1", "path": "group1",
"kind": "group", "kind": "group",
"full_path": "group1", "full_path": "group1",
"parent_id": "null", "parent_id": null,
"members_count_with_descendants": 2 "members_count_with_descendants": 2
} }
``` ```
...@@ -138,7 +138,7 @@ Example response: ...@@ -138,7 +138,7 @@ Example response:
"path": "group1", "path": "group1",
"kind": "group", "kind": "group",
"full_path": "group1", "full_path": "group1",
"parent_id": "null", "parent_id": null,
"members_count_with_descendants": 2 "members_count_with_descendants": 2
} }
``` ```
...@@ -11,11 +11,7 @@ in the table below. ...@@ -11,11 +11,7 @@ in the table below.
| `issues_url` | The URL to the issue in Bugzilla project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. | | `issues_url` | The URL to the issue in Bugzilla project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
| `new_issue_url` | This is the URL to create a new issue in Bugzilla for the project linked to this GitLab project. Note that the `new_issue_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. | | `new_issue_url` | This is the URL to create a new issue in Bugzilla for the project linked to this GitLab project. Note that the `new_issue_url` requires PRODUCT_NAME to be updated with the product/project name in Bugzilla. |
Once you have configured and enabled Bugzilla: Once you have configured and enabled Bugzilla you'll see the Bugzilla link on the GitLab project pages that takes you to the appropriate Bugzilla project.
- the **Issues** link on the GitLab project pages takes you to the appropriate
Bugzilla product page
- clicking **New issue** on the project dashboard takes you to Bugzilla for entering a new issue
## Referencing issues in Bugzilla ## Referencing issues in Bugzilla
......
...@@ -118,7 +118,7 @@ in the table below. ...@@ -118,7 +118,7 @@ in the table below.
| `Transition ID` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** | | `Transition ID` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
After saving the configuration, your GitLab project will be able to interact After saving the configuration, your GitLab project will be able to interact
with all JIRA projects in your JIRA instance. with all JIRA projects in your JIRA instance and you'll see the JIRA link on the GitLab project pages that takes you to the appropriate JIRA project.
![JIRA service page](img/jira_service_page.png) ![JIRA service page](img/jira_service_page.png)
......
...@@ -12,6 +12,8 @@ in the table below. ...@@ -12,6 +12,8 @@ in the table below.
| `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. | | `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
| `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project. **This is currently not being used and will be removed in a future release.** | | `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project. **This is currently not being used and will be removed in a future release.** |
Once you have configured and enabled Redmine you'll see the Redmine link on the GitLab project pages that takes you to the appropriate Redmine project.
As an example, below is a configuration for a project named gitlab-ci. As an example, below is a configuration for a project named gitlab-ci.
![Redmine configuration](img/redmine_configuration.png) ![Redmine configuration](img/redmine_configuration.png)
......
<script> <script>
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { spriteIcon } from '~/lib/utils/common_utils'; import Icon from '~/vue_shared/components/icon.vue';
import expandButton from '~/vue_shared/components/expand_button.vue'; import ExpandButton from '~/vue_shared/components/expand_button.vue';
export default { export default {
name: 'ModalDast', name: 'ModalDast',
components: { components: {
expandButton, ExpandButton,
Icon,
}, },
props: { props: {
title: { title: {
...@@ -27,13 +28,10 @@ ...@@ -27,13 +28,10 @@
instances: { instances: {
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => ([]),
}, },
}, },
computed: { computed: {
cutIcon() {
return spriteIcon('cut');
},
instancesLabel() { instancesLabel() {
return s__('ciReport|Instances'); return s__('ciReport|Instances');
}, },
...@@ -54,7 +52,7 @@ ...@@ -54,7 +52,7 @@
role="dialog" role="dialog"
> >
<div <div
class="modal-dialog" class="modal-dialog modal-lg"
role="document" role="document"
> >
<div class="modal-content"> <div class="modal-content">
...@@ -83,18 +81,21 @@ ...@@ -83,18 +81,21 @@
<li <li
v-for="(instance, i) in instances" v-for="(instance, i) in instances"
:key="i" :key="i"
class="failed" class="mr-widget-code-quality-list-item-modal failed"
> >
<span <icon
class="mr-widget-code-quality-icon" class="mr-widget-code-quality-icon"
v-html="cutIcon" name="status_failed_borderless"
> :size="32"
</span> />
{{ instance.method }} {{ instance.method }}
<a <a
:href="instance.uri" :href="instance.uri"
target="_blank" target="_blank"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
class="prepend-left-5"
> >
{{ instance.uri }} {{ instance.uri }}
</a> </a>
......
...@@ -143,6 +143,7 @@ ...@@ -143,6 +143,7 @@
:href="issue.nameLink" :href="issue.nameLink"
target="_blank" target="_blank"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
class="prepend-left-5"
> >
{{ issue.name }} {{ issue.name }}
</a> </a>
...@@ -155,7 +156,7 @@ ...@@ -155,7 +156,7 @@
type="button" type="button"
@click="openDastModal(issue, index)" @click="openDastModal(issue, index)"
data-toggle="modal" data-toggle="modal"
class="btn-link btn-blank" class="btn-link btn-blank btn-open-modal"
:data-target="modalTargetId" :data-target="modalTargetId"
> >
{{ issue.name }} {{ issue.name }}
......
...@@ -20,30 +20,6 @@ function WeightSelect(els, options = {}) { ...@@ -20,30 +20,6 @@ function WeightSelect(els, options = {}) {
inputField.val(options.selected); inputField.val(options.selected);
} }
updateWeight = function(selected) {
var data;
data = {};
data[abilityName] = {};
data[abilityName].weight = selected != null ? selected : null;
$loading.fadeIn();
$dropdown.trigger('loading.gl.dropdown');
return $.ajax({
type: 'PUT',
dataType: 'json',
url: updateUrl,
data: data
}).done(function(data) {
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
$selectbox.hide();
if (data.weight != null) {
$value.html(`<strong>${data.weight}</strong>`);
} else {
$value.html('<span class="no-value">None</span>');
}
return $sidebarCollapsedValue.html(data.weight);
});
};
return $dropdown.glDropdown({ return $dropdown.glDropdown({
selectable: true, selectable: true,
fieldName, fieldName,
...@@ -70,13 +46,8 @@ function WeightSelect(els, options = {}) { ...@@ -70,13 +46,8 @@ function WeightSelect(els, options = {}) {
e.preventDefault(); e.preventDefault();
selected = inputField.val(); selected = inputField.val();
options.handleClick(selected); options.handleClick(selected);
} else if ($(dropdown).is(".js-filter-submit")) {
return $(dropdown).parents('form').submit();
} else if ($dropdown.is('.js-issuable-form-weight')) { } else if ($dropdown.is('.js-issuable-form-weight')) {
e.preventDefault(); e.preventDefault();
} else {
selected = inputField.val();
return updateWeight(selected);
} }
} }
}); });
......
...@@ -8,7 +8,7 @@ module EpicsHelper ...@@ -8,7 +8,7 @@ module EpicsHelper
name: author.name, name: author.name,
url: user_path(author), url: user_path(author),
username: "@#{author.username}", username: "@#{author.username}",
src: avatar_icon(@epic.author) src: avatar_icon_for_user(@epic.author)
}, },
start_date: @epic.start_date, start_date: @epic.start_date,
end_date: @epic.end_date end_date: @epic.end_date
......
...@@ -184,8 +184,7 @@ module Geo ...@@ -184,8 +184,7 @@ module Geo
# Remove the deleted path in case it exists, but it may not be there # Remove the deleted path in case it exists, but it may not be there
gitlab_shell.remove_repository(project.repository_storage_path, deleted_disk_path_temp) gitlab_shell.remove_repository(project.repository_storage_path, deleted_disk_path_temp)
# Move the original repository out of the way if project.repository_exists? && !gitlab_shell.mv_repository(project.repository_storage_path, repository.disk_path, deleted_disk_path_temp)
unless gitlab_shell.mv_repository(project.repository_storage_path, repository.disk_path, deleted_disk_path_temp)
raise Gitlab::Shell::Error, 'Can not move original repository out of the way' raise Gitlab::Shell::Error, 'Can not move original repository out of the way'
end end
......
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
%span{ style: "font-weight: 600;color:#333333;" } Merge request %span{ style: "font-weight: 600;color:#333333;" } Merge request
%a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference %a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference
%span was approved by %span was approved by
%img.avatar{ height: "24", src: avatar_icon(@approved_by, 24, only_path: false), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@approved_by, 24, only_path: false), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }/
%a.muted{ href: user_url(@approved_by), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@approved_by), style: "color:#333333;text-decoration:none;" }
= @approved_by.name = @approved_by.name
%tr.spacer %tr.spacer
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(@merge_request.author, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@merge_request.author, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: user_url(@merge_request.author), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@merge_request.author), style: "color:#333333;text-decoration:none;" }
= @merge_request.author.name = @merge_request.author.name
...@@ -130,7 +130,7 @@ ...@@ -130,7 +130,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(@merge_request.assignee, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@merge_request.assignee, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: user_url(@merge_request.assignee), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@merge_request.assignee), style: "color:#333333;text-decoration:none;" }
= @merge_request.assignee.name = @merge_request.assignee.name
......
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
%span{ style: "font-weight: 600;color:#333333;" } Merge request %span{ style: "font-weight: 600;color:#333333;" } Merge request
%a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference %a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference
%span was unapproved by %span was unapproved by
%img.avatar{ height: "24", src: avatar_icon(@unapproved_by, 24), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@unapproved_by, 24), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }/
%a.muted{ href: user_url(@unapproved_by), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@unapproved_by), style: "color:#333333;text-decoration:none;" }
= @unapproved_by.name = @unapproved_by.name
%tr.spacer %tr.spacer
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(@merge_request.author, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@merge_request.author, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: user_url(@merge_request.author), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@merge_request.author), style: "color:#333333;text-decoration:none;" }
= @merge_request.author.name = @merge_request.author.name
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
%tbody %tbody
%tr %tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon(@merge_request.assignee, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ %img.avatar{ height: "24", src: avatar_icon_for_user(@merge_request.assignee, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: user_url(@merge_request.assignee), style: "color:#333333;text-decoration:none;" } %a.muted{ href: user_url(@merge_request.assignee), style: "color:#333333;text-decoration:none;" }
= @merge_request.assignee.name = @merge_request.assignee.name
......
...@@ -141,7 +141,7 @@ describe Groups::EpicsController do ...@@ -141,7 +141,7 @@ describe Groups::EpicsController do
show_epic(:json) show_epic(:json)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to match_response_schema('entities/epic') expect(response).to match_response_schema('entities/epic', dir: 'ee')
end end
context 'with unauthorized user' do context 'with unauthorized user' do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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