Commit 8a2f31f7 authored by Constance Okoghenun's avatar Constance Okoghenun

resolved conflicts with dispatcher.js

parents 38960614 c2501d08
...@@ -398,9 +398,9 @@ For issues related to the open source stewardship of GitLab, ...@@ -398,9 +398,9 @@ For issues related to the open source stewardship of GitLab,
there is the ~"stewardship" label. there is the ~"stewardship" label.
This label is to be used for issues in which the stewardship of GitLab This label is to be used for issues in which the stewardship of GitLab
is a topic of discussion. For instance if GitLab Inc. is planning to remove is a topic of discussion. For instance if GitLab Inc. is planning to add
features from GitLab CE to make exclusive in GitLab EE, related issues features from GitLab EE to GitLab CE, related issues would be labelled with
would be labelled with ~"stewardship". ~"stewardship".
A recent example of this was the issue for A recent example of this was the issue for
[bringing the time tracking API to GitLab CE][time-tracking-issue]. [bringing the time tracking API to GitLab CE][time-tracking-issue].
......
...@@ -426,7 +426,7 @@ group :ed25519 do ...@@ -426,7 +426,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.84.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.85.0', require: 'gitaly'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1' gem 'google-protobuf', '= 3.5.1'
......
...@@ -309,7 +309,7 @@ GEM ...@@ -309,7 +309,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.84.0) gitaly-proto (0.85.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -1091,7 +1091,7 @@ DEPENDENCIES ...@@ -1091,7 +1091,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.84.0) gitaly-proto (~> 0.85.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
......
<script> <script>
import Sortable from 'vendor/Sortable'; import Sortable from 'vendor/Sortable';
import boardNewIssue from './board_new_issue'; import boardNewIssue from './board_new_issue.vue';
import boardCard from './board_card.vue'; import boardCard from './board_card.vue';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
......
/* global ListIssue */ <script>
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue'; import ProjectSelect from 'ee/boards/components/project_select.vue'; // eslint-disable-line import/first
import ListIssue from '../models/issue';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
export default { export default {
name: 'BoardNewIssue', name: 'BoardNewIssue',
components: {
ProjectSelect,
},
props: { props: {
groupId: { groupId: {
type: Number, type: Number,
...@@ -24,9 +28,6 @@ export default { ...@@ -24,9 +28,6 @@ export default {
selectedProject: {}, selectedProject: {},
}; };
}, },
components: {
'project-select': ProjectSelect,
},
computed: { computed: {
disabled() { disabled() {
if (this.groupId) { if (this.groupId) {
...@@ -35,6 +36,10 @@ export default { ...@@ -35,6 +36,10 @@ export default {
return this.title === ''; return this.title === '';
}, },
}, },
mounted() {
this.$refs.input.focus();
eventHub.$on('setSelectedProject', this.setSelectedProject);
},
methods: { methods: {
submit(e) { submit(e) {
e.preventDefault(); e.preventDefault();
...@@ -81,49 +86,57 @@ export default { ...@@ -81,49 +86,57 @@ export default {
this.selectedProject = selectedProject; this.selectedProject = selectedProject;
}, },
}, },
mounted() { };
this.$refs.input.focus(); </script>
eventHub.$on('setSelectedProject', this.setSelectedProject);
}, <template>
template: `
<div class="board-new-issue-form"> <div class="board-new-issue-form">
<div class="card"> <div class="card">
<form @submit="submit($event)"> <form @submit="submit($event)">
<div class="flash-container" <div
v-if="error"> class="flash-container"
v-if="error"
>
<div class="flash-alert"> <div class="flash-alert">
An error occurred. Please try again. An error occurred. Please try again.
</div> </div>
</div> </div>
<label class="label-light" <label
:for="list.id + '-title'"> class="label-light"
:for="list.id + '-title'"
>
Title Title
</label> </label>
<input class="form-control" <input
class="form-control"
type="text" type="text"
v-model="title" v-model="title"
ref="input" ref="input"
autocomplete="off" autocomplete="off"
:id="list.id + '-title'" /> :id="list.id + '-title'"
/>
<project-select <project-select
v-if="groupId" v-if="groupId"
:groupId="groupId" :group-id="groupId"
/> />
<div class="clearfix prepend-top-10"> <div class="clearfix prepend-top-10">
<button class="btn btn-success pull-left" <button
class="btn btn-success pull-left"
type="submit" type="submit"
:disabled="disabled" :disabled="disabled"
ref="submit-button"> ref="submit-button"
>
Submit issue Submit issue
</button> </button>
<button class="btn btn-default pull-right" <button
class="btn btn-default pull-right"
type="button" type="button"
@click="cancel"> @click="cancel"
>
Cancel Cancel
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
`, </template>
};
...@@ -2,16 +2,17 @@ ...@@ -2,16 +2,17 @@
import _ from 'underscore'; import _ from 'underscore';
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../flash';
import { __ } from '../locale'; import Flash from '~/flash';
import { __ } from '~/locale';
import FilteredSearchBoards from './filtered_search_boards'; import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub'; import eventHub from './eventhub';
import sidebarEventHub from '../sidebar/event_hub'; import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
import './models/issue'; import './models/issue';
import './models/label'; import './models/label';
import './models/list'; import './models/list';
import './models/milestone'; import './models/milestone';
import './models/project';
import './models/assignee'; import './models/assignee';
import './stores/boards_store'; import './stores/boards_store';
import './stores/modal_store'; import './stores/modal_store';
...@@ -23,14 +24,15 @@ import './components/board'; ...@@ -23,14 +24,15 @@ import './components/board';
import './components/board_sidebar'; import './components/board_sidebar';
import './components/new_list_dropdown'; import './components/new_list_dropdown';
import './components/modal/index'; import './components/modal/index';
import '../vue_shared/vue_resource_interceptor'; import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/first
import './components/boards_selector'; import 'ee/boards/models/project'; // eslint-disable-line import/first
import collapseIcon from './icons/fullscreen_collapse.svg'; import 'ee/boards/components/boards_selector'; // eslint-disable-line import/first
import expandIcon from './icons/fullscreen_expand.svg'; import collapseIcon from 'ee/boards/icons/fullscreen_collapse.svg'; // eslint-disable-line import/first
import tooltip from '../vue_shared/directives/tooltip'; import expandIcon from 'ee/boards/icons/fullscreen_expand.svg'; // eslint-disable-line import/first
import tooltip from '~/vue_shared/directives/tooltip'; // eslint-disable-line import/first
$(() => { export default () => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
...@@ -366,4 +368,4 @@ $(() => { ...@@ -366,4 +368,4 @@ $(() => {
'boards-selector': gl.issueBoards.BoardsSelector, 'boards-selector': gl.issueBoards.BoardsSelector,
} }
}); });
}); };
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */ /* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
/* global DocumentTouch */ /* global DocumentTouch */
import sortableConfig from '../../sortable/sortable_config'; import sortableConfig from 'ee/sortable/sortable_config';
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
/* global ListAssignee */ /* global ListAssignee */
import Vue from 'vue'; import Vue from 'vue';
import IssueProject from './project'; import IssueProject from 'ee/boards/models/project';
class ListIssue { class ListIssue {
constructor (obj, defaultAvatar) { constructor (obj, defaultAvatar) {
...@@ -122,3 +122,5 @@ class ListIssue { ...@@ -122,3 +122,5 @@ class ListIssue {
} }
window.ListIssue = ListIssue; window.ListIssue = ListIssue;
export default ListIssue;
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import boardsStoreEE from 'ee/boards/stores/boards_store_ee'; import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
import { getUrlParamsArray } from '../../lib/utils/common_utils'; import { getUrlParamsArray } from '~/lib/utils/common_utils';
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
......
...@@ -15,7 +15,7 @@ const CommitPipelinesTable = Vue.extend(commitPipelinesTable); ...@@ -15,7 +15,7 @@ const CommitPipelinesTable = Vue.extend(commitPipelinesTable);
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.CommitPipelinesTable = CommitPipelinesTable; window.gl.CommitPipelinesTable = CommitPipelinesTable;
document.addEventListener('DOMContentLoaded', () => { export default () => {
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
if (pipelineTableViewEl) { if (pipelineTableViewEl) {
...@@ -43,4 +43,4 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -43,4 +43,4 @@ document.addEventListener('DOMContentLoaded', () => {
pipelineTableViewEl.appendChild(table.$el); pipelineTableViewEl.appendChild(table.$el);
} }
} }
}); };
import Vue from 'vue'; import Vue from 'vue';
import deployKeysApp from './components/app.vue'; import deployKeysApp from './components/app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({ export default () => new Vue({
el: document.getElementById('js-deploy-keys'), el: document.getElementById('js-deploy-keys'),
components: { components: {
deployKeysApp, deployKeysApp,
...@@ -18,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -18,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}, },
}); });
}, },
})); });
...@@ -6,43 +6,21 @@ import GlFieldErrors from './gl_field_errors'; ...@@ -6,43 +6,21 @@ import GlFieldErrors from './gl_field_errors';
import Shortcuts from './shortcuts'; import Shortcuts from './shortcuts';
import SearchAutocomplete from './search_autocomplete'; import SearchAutocomplete from './search_autocomplete';
var Dispatcher; function initSearch() {
// Only when search form is present
(function() { if ($('.search').length) {
Dispatcher = (function() { return new SearchAutocomplete();
function Dispatcher() {
this.initSearch();
this.initFieldErrors();
this.initPageScripts();
}
Dispatcher.prototype.initPageScripts = function() {
var path, shortcut_handler;
const page = $('body').attr('data-page');
if (!page) {
return false;
} }
}
const fail = () => Flash('Error loading dynamic module'); function initFieldErrors() {
const callDefault = m => m.default(); $('.gl-show-field-errors').each((i, form) => {
new GlFieldErrors(form);
path = page.split(':');
shortcut_handler = null;
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
gfm.setup($(el), {
emojis: true,
members: enableGFM,
issues: enableGFM,
milestones: enableGFM,
mergeRequests: enableGFM,
labels: enableGFM,
});
}); });
}
const shortcutHandlerPages = [ function initPageShortcuts(page) {
const pagesWithCustomShortcuts = [
'projects:activity', 'projects:activity',
'projects:artifacts:browse', 'projects:artifacts:browse',
'projects:artifacts:file', 'projects:artifacts:file',
...@@ -66,112 +44,42 @@ var Dispatcher; ...@@ -66,112 +44,42 @@ var Dispatcher;
'groups:show', 'groups:show',
]; ];
if (shortcutHandlerPages.indexOf(page) !== -1) { if (pagesWithCustomShortcuts.indexOf(page) === -1) {
shortcut_handler = true;
}
switch (path[0]) {
case 'admin':
switch (path[1]) {
case 'broadcast_messages':
import('./pages/admin/broadcast_messages')
.then(callDefault)
.catch(fail);
break;
case 'cohorts':
import('./pages/admin/cohorts')
.then(callDefault)
.catch(fail);
break;
case 'groups':
switch (path[2]) {
case 'show':
import('./pages/admin/groups/show')
.then(callDefault)
.catch(fail);
break;
}
break;
case 'projects':
import('./pages/admin/projects')
.then(callDefault)
.catch(fail);
break;
case 'labels':
switch (path[2]) {
case 'new':
import('./pages/admin/labels/new')
.then(callDefault)
.catch(fail);
break;
case 'edit':
import('./pages/admin/labels/edit')
.then(callDefault)
.catch(fail);
break;
}
case 'abuse_reports':
import('./pages/admin/abuse_reports')
.then(callDefault)
.catch(fail);
break;
}
break;
case 'projects':
import('./pages/projects')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
switch (path[1]) {
case 'compare':
import('./pages/projects/compare')
.then(callDefault)
.catch(fail);
break;
case 'create':
case 'new':
import('./pages/projects/new')
.then(callDefault)
.catch(fail);
break;
case 'wikis':
import('./pages/projects/wikis')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
break;
}
break;
}
// If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) {
new Shortcuts(); new Shortcuts();
} }
}
function initGFMInput() {
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete);
gfm.setup($(el), {
emojis: true,
members: enableGFM,
issues: enableGFM,
milestones: enableGFM,
mergeRequests: enableGFM,
labels: enableGFM,
});
});
}
function initPerformanceBar() {
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
.catch(fail); .catch(() => Flash('Error loading performance bar module'));
} }
}; }
Dispatcher.prototype.initSearch = function() {
// Only when search form is present
if ($('.search').length) {
return new SearchAutocomplete();
}
};
Dispatcher.prototype.initFieldErrors = function() {
$('.gl-show-field-errors').each((i, form) => {
new GlFieldErrors(form);
});
};
return Dispatcher; export default () => {
})(); initSearch();
})(); initFieldErrors();
export default function initDispatcher() { const page = $('body').attr('data-page');
return new Dispatcher(); if (page) {
} initPageShortcuts(page);
initGFMInput();
initPerformanceBar();
}
};
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
/** /**
* Render environments table. * Render environments table.
*/ */
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import environmentItem from './environment_item.vue'; import environmentItem from './environment_item.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import deployBoard from './deploy_board_component.vue'; import deployBoard from 'ee/environments/components/deploy_board_component.vue'; // eslint-disable-line import/first
export default { export default {
components: { components: {
......
...@@ -5,7 +5,7 @@ import Translate from '../../vue_shared/translate'; ...@@ -5,7 +5,7 @@ import Translate from '../../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => new Vue({ export default () => new Vue({
el: '#environments-folder-list-view', el: '#environments-folder-list-view',
components: { components: {
environmentsFolderApp, environmentsFolderApp,
...@@ -32,4 +32,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -32,4 +32,4 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}, },
}); });
}, },
})); });
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import timeAgoMixin from '../../vue_shared/mixins/timeago';
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue'; import timeAgoMixin from '~/vue_shared/mixins/timeago';
import fileStatusIcon from './repo_file_status_icon.vue'; import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import newDropdown from './new_dropdown/index.vue'; import newDropdown from './new_dropdown/index.vue';
import fileIcon from '../../vue_shared/components/file_icon.vue';
import changedFileIcon from './changed_file_icon.vue'; import fileStatusIcon from 'ee/ide/components/repo_file_status_icon.vue'; // eslint-disable-line import/first
import changedFileIcon from 'ee/ide/components/changed_file_icon.vue'; // eslint-disable-line import/first
export default { export default {
components: { components: {
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import fileStatusIcon from './repo_file_status_icon.vue';
import fileIcon from '../../vue_shared/components/file_icon.vue'; import fileIcon from '~/vue_shared/components/file_icon.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import changedFileIcon from './changed_file_icon.vue';
import fileStatusIcon from 'ee/ide/components/repo_file_status_icon.vue';
import changedFileIcon from 'ee/ide/components/changed_file_icon.vue';
export default { export default {
components: { components: {
......
...@@ -5,7 +5,7 @@ import Disposable from './common/disposable'; ...@@ -5,7 +5,7 @@ import Disposable from './common/disposable';
import ModelManager from './common/model_manager'; import ModelManager from './common/model_manager';
import editorOptions from './editor_options'; import editorOptions from './editor_options';
import gitlabTheme from './themes/gl_theme'; import gitlabTheme from 'ee/ide/lib/themes/gl_theme'; // eslint-disable-line import/first
export default class Editor { export default class Editor {
static create(monaco) { static create(monaco) {
......
...@@ -213,7 +213,7 @@ export default class LabelsSelect { ...@@ -213,7 +213,7 @@ export default class LabelsSelect {
} }
} }
if (label.duplicate) { if (label.duplicate) {
color = gl.DropdownUtils.duplicateLabelColor(label.color); color = DropdownUtils.duplicateLabelColor(label.color);
} }
else { else {
if (label.color != null) { if (label.color != null) {
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, camelcase, comma-dangle, consistent-return, max-len */
import ShortcutsNetwork from '../shortcuts_network';
import Network from './network';
$(function() {
if (!$(".network-graph").length) return;
var network_graph;
network_graph = new Network({
url: $(".network-graph").attr('data-url'),
commit_url: $(".network-graph").attr('data-commit-url'),
ref: $(".network-graph").attr('data-ref'),
commit_id: $(".network-graph").attr('data-commit-id')
});
return new ShortcutsNetwork(network_graph.branch_graph);
});
import AbuseReports from './abuse_reports'; import AbuseReports from './abuse_reports';
export default () => new AbuseReports(); document.addEventListener('DOMContentLoaded', () => new AbuseReports());
...@@ -3,7 +3,7 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -3,7 +3,7 @@ import axios from '~/lib/utils/axios_utils';
import flash from '~/flash'; import flash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default function initBroadcastMessagesForm() { export default () => {
$('input#broadcast_message_color').on('input', function onMessageColorInput() { $('input#broadcast_message_color').on('input', function onMessageColorInput() {
const previewColor = $(this).val(); const previewColor = $(this).val();
$('div.broadcast-message-preview').css('background-color', previewColor); $('div.broadcast-message-preview').css('background-color', previewColor);
...@@ -32,4 +32,4 @@ export default function initBroadcastMessagesForm() { ...@@ -32,4 +32,4 @@ export default function initBroadcastMessagesForm() {
.catch(() => flash(__('An error occurred while rendering preview broadcast message'))); .catch(() => flash(__('An error occurred while rendering preview broadcast message')));
} }
}, 250)); }, 250));
} };
import initBroadcastMessagesForm from './broadcast_message'; import initBroadcastMessagesForm from './broadcast_message';
export default () => initBroadcastMessagesForm(); document.addEventListener('DOMContentLoaded', initBroadcastMessagesForm);
import initUsagePing from './usage_ping'; import initUsagePing from './usage_ping';
export default () => initUsagePing(); document.addEventListener('DOMContentLoaded', initUsagePing);
import UsersSelect from '../../../../users_select'; import UsersSelect from '../../../../users_select';
export default () => new UsersSelect(); document.addEventListener('DOMContentLoaded', () => new UsersSelect());
import Labels from '../../../../labels'; import Labels from '../../../../labels';
export default () => new Labels(); document.addEventListener('DOMContentLoaded', () => new Labels());
import Labels from '../../../../labels'; import Labels from '../../../../labels';
export default () => new Labels(); document.addEventListener('DOMContentLoaded', () => new Labels());
import ProjectsList from '../../../projects_list'; import ProjectsList from '../../../projects_list';
import NamespaceSelect from '../../../namespace_select'; import NamespaceSelect from '../../../namespace_select';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new new ProjectsList(); // eslint-disable-line no-new
document.querySelectorAll('.js-namespace-select') document.querySelectorAll('.js-namespace-select')
.forEach(dropdown => new NamespaceSelect({ dropdown })); .forEach(dropdown => new NamespaceSelect({ dropdown }));
}; });
import UsersSelect from '~/users_select';
import ShortcutsNavigation from '~/shortcuts_navigation';
import initBoards from '~/boards';
document.addEventListener('DOMContentLoaded', () => {
new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
initBoards();
});
import UsersSelect from '~/users_select'; import UsersSelect from '~/users_select';
import ShortcutsNavigation from '~/shortcuts_navigation'; import ShortcutsNavigation from '~/shortcuts_navigation';
import initBoards from '~/boards';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
initBoards();
}); });
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new MiniPipelineGraph({ new MiniPipelineGraph({
container: '.js-commit-pipeline-graph', container: '.js-commit-pipeline-graph',
}).bindEvents(); }).bindEvents();
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
initPipelines();
}); });
...@@ -5,6 +5,7 @@ import ShortcutsNavigation from '~/shortcuts_navigation'; ...@@ -5,6 +5,7 @@ import ShortcutsNavigation from '~/shortcuts_navigation';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initNotes from '~/init_notes'; import initNotes from '~/init_notes';
import initChangesDropdown from '~/init_changes_dropdown'; import initChangesDropdown from '~/init_changes_dropdown';
import initDiffNotes from '~/diff_notes/diff_notes_bundle';
import { fetchCommitMergeRequests } from '~/commit_merge_requests'; import { fetchCommitMergeRequests } from '~/commit_merge_requests';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
...@@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => {
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop); initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
fetchCommitMergeRequests(); fetchCommitMergeRequests();
initDiffNotes();
}); });
import initCompareAutocomplete from '~/compare_autocomplete'; import initCompareAutocomplete from '~/compare_autocomplete';
export default () => { document.addEventListener('DOMContentLoaded', initCompareAutocomplete);
initCompareAutocomplete();
};
import initEnvironmentsFolderBundle from '~/environments/folder/environments_folder_bundle';
document.addEventListener('DOMContentLoaded', initEnvironmentsFolderBundle);
import Project from './project'; import Project from './project';
import ShortcutsNavigation from '../../shortcuts_navigation'; import ShortcutsNavigation from '../../shortcuts_navigation';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new Project(); // eslint-disable-line no-new new Project(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
}; });
import Compare from '~/compare'; import Compare from '~/compare';
import MergeRequest from '~/merge_request'; import MergeRequest from '~/merge_request';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare'); const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
...@@ -14,5 +15,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -14,5 +15,6 @@ document.addEventListener('DOMContentLoaded', () => {
new MergeRequest({ // eslint-disable-line no-new new MergeRequest({ // eslint-disable-line no-new
action: mrNewSubmitNode.dataset.mrSubmitAction, action: mrNewSubmitNode.dataset.mrSubmitAction,
}); });
initPipelines();
} }
}); });
...@@ -7,6 +7,8 @@ import ShortcutsIssuable from '~/shortcuts_issuable'; ...@@ -7,6 +7,8 @@ import ShortcutsIssuable from '~/shortcuts_issuable';
import Diff from '~/diff'; import Diff from '~/diff';
import { handleLocationHash } from '~/lib/utils/common_utils'; import { handleLocationHash } from '~/lib/utils/common_utils';
import howToMerge from '~/how_to_merge'; import howToMerge from '~/how_to_merge';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initWidget from '../../../../vue_merge_request_widget';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new Diff(); // eslint-disable-line no-new new Diff(); // eslint-disable-line no-new
...@@ -15,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -15,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => {
initIssuableSidebar(); initIssuableSidebar();
initNotes(); initNotes();
initDiffNotes(); initDiffNotes();
initPipelines();
const mrShowNode = document.querySelector('.merge-request'); const mrShowNode = document.querySelector('.merge-request');
...@@ -25,4 +28,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -25,4 +28,5 @@ document.addEventListener('DOMContentLoaded', () => {
new ShortcutsIssuable(true); // eslint-disable-line no-new new ShortcutsIssuable(true); // eslint-disable-line no-new
handleLocationHash(); handleLocationHash();
howToMerge(); howToMerge();
initWidget();
}); });
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */
import BranchGraph from './branch_graph'; import BranchGraph from '../../../network/branch_graph';
export default (function() { export default (function() {
function Network(opts) { function Network(opts) {
......
import ShortcutsNetwork from '../../../../shortcuts_network';
import Network from '../network';
document.addEventListener('DOMContentLoaded', () => {
if (!$('.network-graph').length) return;
const networkGraph = new Network({
url: $('.network-graph').attr('data-url'),
commit_url: $('.network-graph').attr('data-commit-url'),
ref: $('.network-graph').attr('data-ref'),
commit_id: $('.network-graph').attr('data-commit-id'),
});
// eslint-disable-next-line no-new
new ShortcutsNetwork(networkGraph.branch_graph);
});
...@@ -2,8 +2,8 @@ import ProjectNew from '../shared/project_new'; ...@@ -2,8 +2,8 @@ import ProjectNew from '../shared/project_new';
import initProjectVisibilitySelector from '../../../project_visibility'; import initProjectVisibilitySelector from '../../../project_visibility';
import initProjectNew from '../../../projects/project_new'; import initProjectNew from '../../../projects/project_new';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new ProjectNew(); // eslint-disable-line no-new new ProjectNew(); // eslint-disable-line no-new
initProjectVisibilitySelector(); initProjectVisibilitySelector();
initProjectNew.bindEvents(); initProjectNew.bindEvents();
}; });
import Vue from 'vue'; import Vue from 'vue';
import PipelinesStore from './stores/pipelines_store'; import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
import pipelinesComponent from './components/pipelines.vue'; import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
import Translate from '../vue_shared/translate'; import Translate from '../../../../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
......
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import initDeployKeys from '~/deploy_keys';
document.addEventListener('DOMContentLoaded', initSettingsPanels); document.addEventListener('DOMContentLoaded', () => {
initDeployKeys();
initSettingsPanels();
});
...@@ -3,9 +3,9 @@ import ShortcutsWiki from '../../../shortcuts_wiki'; ...@@ -3,9 +3,9 @@ import ShortcutsWiki from '../../../shortcuts_wiki';
import ZenMode from '../../../zen_mode'; import ZenMode from '../../../zen_mode';
import GLForm from '../../../gl_form'; import GLForm from '../../../gl_form';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form'), true); // eslint-disable-line no-new new GLForm($('.wiki-form'), true); // eslint-disable-line no-new
}; });
<script> <script>
import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import linkedPipelinesColumn from './linked_pipelines_column.vue';
import stageColumnComponent from './stage_column_component.vue'; import stageColumnComponent from './stage_column_component.vue';
import linkedPipelinesColumn from 'ee/pipelines/components/graph/linked_pipelines_column.vue'; // eslint-disable-line import/first
export default { export default {
components: { components: {
linkedPipelinesColumn, linkedPipelinesColumn,
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import '../../vue_shared/vue_resource_interceptor';
Vue.use(VueResource); Vue.use(VueResource);
......
...@@ -9,13 +9,12 @@ export default class ShortcutsIssuable extends Shortcuts { ...@@ -9,13 +9,12 @@ export default class ShortcutsIssuable extends Shortcuts {
super(); super();
this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form'); this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form');
this.editBtn = document.querySelector('.js-issuable-edit');
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee')); Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee'));
Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone')); Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone'));
Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels')); Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels'));
Mousetrap.bind('r', this.replyWithSelectedText.bind(this)); Mousetrap.bind('r', this.replyWithSelectedText.bind(this));
Mousetrap.bind('e', this.editIssue.bind(this)); Mousetrap.bind('e', ShortcutsIssuable.editIssue);
if (isMergeRequest) { if (isMergeRequest) {
this.enabledHelp.push('.hidden-shortcut.merge_requests'); this.enabledHelp.push('.hidden-shortcut.merge_requests');
...@@ -58,10 +57,10 @@ export default class ShortcutsIssuable extends Shortcuts { ...@@ -58,10 +57,10 @@ export default class ShortcutsIssuable extends Shortcuts {
return false; return false;
} }
editIssue() { static editIssue() {
// Need to click the element as on issues, editing is inline // Need to click the element as on issues, editing is inline
// on merge request, editing is on a different page // on merge request, editing is on a different page
this.editBtn.click(); document.querySelector('.js-issuable-edit').click();
return false; return false;
} }
......
<script> <script>
/* eslint-disable vue/require-default-prop */ /* eslint-disable vue/require-default-prop */
import pipelineStage from '../../pipelines/components/stage.vue'; import pipelineStage from '~/pipelines/components/stage.vue';
import ciIcon from '../../vue_shared/components/ci_icon.vue'; import ciIcon from '~/vue_shared/components/ci_icon.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import linkedPipelinesMiniList from '../../vue_shared/components/linked_pipelines_mini_list.vue'; import linkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue';
export default { export default {
name: 'MRWidgetPipeline', name: 'MRWidgetPipeline',
......
...@@ -6,7 +6,7 @@ import Translate from '../vue_shared/translate'; ...@@ -6,7 +6,7 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => { export default () => {
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo; gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
const vm = new Vue(mrWidgetOptions); const vm = new Vue(mrWidgetOptions);
...@@ -14,4 +14,4 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -14,4 +14,4 @@ document.addEventListener('DOMContentLoaded', () => {
window.gl.mrWidget = { window.gl.mrWidget = {
checkStatus: vm.checkStatus, checkStatus: vm.checkStatus,
}; };
}); };
...@@ -200,17 +200,9 @@ ...@@ -200,17 +200,9 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
font-size: 0; font-size: 0;
div {
display: inline;
}
.fa-spinner { .fa-spinner {
font-size: 12px; font-size: 12px;
} }
span {
font-size: 6px;
}
} }
.ci-status-link { .ci-status-link {
......
...@@ -13,6 +13,16 @@ ...@@ -13,6 +13,16 @@
display: inline-block; display: inline-block;
} }
.issuable-meta {
.author_link {
display: inline-block;
}
.issuable-comments {
height: 18px;
}
}
.icon-merge-request-unmerged { .icon-merge-request-unmerged {
height: 13px; height: 13px;
margin-bottom: 3px; margin-bottom: 3px;
......
...@@ -50,7 +50,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -50,7 +50,7 @@ class Admin::GroupsController < Admin::ApplicationController
def members_update def members_update
member_params = params.permit(:user_ids, :access_level, :expires_at) member_params = params.permit(:user_ids, :access_level, :expires_at)
result = Members::CreateService.new(@group, current_user, member_params.merge(limit: -1)).execute result = Members::CreateService.new(current_user, member_params.merge(limit: -1)).execute(@group)
if result[:status] == :success if result[:status] == :success
redirect_to [:admin, @group], notice: 'Users were successfully added.' redirect_to [:admin, @group], notice: 'Users were successfully added.'
......
...@@ -3,20 +3,31 @@ module MembershipActions ...@@ -3,20 +3,31 @@ module MembershipActions
def create def create
create_params = params.permit(:user_ids, :access_level, :expires_at) create_params = params.permit(:user_ids, :access_level, :expires_at)
result = Members::CreateService.new(membershipable, current_user, create_params).execute result = Members::CreateService.new(current_user, create_params).execute(membershipable)
redirect_url = members_page_url
if result[:status] == :success if result[:status] == :success
redirect_to redirect_url, notice: 'Users were successfully added.' redirect_to members_page_url, notice: 'Users were successfully added.'
else else
redirect_to redirect_url, alert: result[:message] redirect_to members_page_url, alert: result[:message]
end
end
def update
update_params = params.require(root_params_key).permit(:access_level, :expires_at)
member = membershipable.members_and_requesters.find(params[:id])
member = Members::UpdateService
.new(current_user, update_params)
.execute(member)
.present(current_user: current_user)
respond_to do |format|
format.js { render 'shared/members/update', locals: { member: member } }
end end
end end
def destroy def destroy
Members::DestroyService.new(membershipable, current_user, params) member = membershipable.members_and_requesters.find(params[:id])
.execute(:all) Members::DestroyService.new(current_user).execute(member)
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -36,16 +47,17 @@ module MembershipActions ...@@ -36,16 +47,17 @@ module MembershipActions
end end
def approve_access_request def approve_access_request
member = Members::ApproveAccessRequestService.new(membershipable, current_user, params).execute access_requester = membershipable.requesters.find(params[:id])
Members::ApproveAccessRequestService
log_audit_event(member, action: :create) .new(current_user, params)
.execute(access_requester)
redirect_to members_page_url redirect_to members_page_url
end end
def leave def leave
member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id) member = membershipable.members_and_requesters.find_by!(user_id: current_user.id)
.execute(:all) Members::DestroyService.new(current_user).execute(member)
notice = notice =
if member.request? if member.request?
...@@ -64,22 +76,43 @@ module MembershipActions ...@@ -64,22 +76,43 @@ module MembershipActions
end end
end end
def resend_invite
member = membershipable.members.find(params[:id])
if member.invite?
member.resend_invite
redirect_to members_page_url, notice: 'The invitation was successfully resent.'
else
redirect_to members_page_url, alert: 'The invitation has already been accepted.'
end
end
protected protected
def membershipable def membershipable
raise NotImplementedError raise NotImplementedError
end end
def log_audit_event(member, options = {}) def root_params_key
AuditEventService.new(current_user, membershipable, options) case membershipable
.for_member(member).security_event when Namespace
:group_member
when Project
:project_member
else
raise "Unknown membershipable type: #{membershipable}!"
end
end end
def members_page_url def members_page_url
if membershipable.is_a?(Project) case membershipable
when Namespace
polymorphic_url([membershipable, :members])
when Project
project_project_members_path(membershipable) project_project_members_path(membershipable)
else else
polymorphic_url([membershipable, :members]) raise "Unknown membershipable type: #{membershipable}!"
end end
end end
......
...@@ -19,10 +19,6 @@ class Groups::ApplicationController < ApplicationController ...@@ -19,10 +19,6 @@ class Groups::ApplicationController < ApplicationController
@projects ||= GroupProjectsFinder.new(group: group, current_user: current_user).execute @projects ||= GroupProjectsFinder.new(group: group, current_user: current_user).execute
end end
def group_merge_requests
@group_merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id).execute
end
def authorize_admin_group! def authorize_admin_group!
unless can?(current_user, :admin_group, group) unless can?(current_user, :admin_group, group)
return render_404 return render_404
......
...@@ -34,39 +34,6 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -34,39 +34,6 @@ class Groups::GroupMembersController < Groups::ApplicationController
@group_member = @group.group_members.new @group_member = @group.group_members.new
end end
def update
@group_member = @group.members_and_requesters.find(params[:id])
.present(current_user: current_user)
return render_403 unless can?(current_user, :update_group_member, @group_member)
old_access_level = @group_member.human_access
if @group_member.update_attributes(member_params)
log_audit_event(@group_member, action: :update, old_access_level: old_access_level)
end
end
def resend_invite
redirect_path = group_group_members_path(@group)
@group_member = @group.group_members.find(params[:id])
if @group_member.invite?
@group_member.resend_invite
redirect_to redirect_path, notice: 'The invitation was successfully resent.'
else
redirect_to redirect_path, alert: 'The invitation has already been accepted.'
end
end
protected
def member_params
params.require(:group_member).permit(:access_level, :user_id, :expires_at)
end
# MembershipActions concern # MembershipActions concern
alias_method :membershipable, :group alias_method :membershipable, :group
end end
...@@ -15,7 +15,6 @@ class GroupsController < Groups::ApplicationController ...@@ -15,7 +15,6 @@ class GroupsController < Groups::ApplicationController
before_action :authorize_create_group!, only: [:new] before_action :authorize_create_group!, only: [:new]
before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests] before_action :group_projects, only: [:projects, :activity, :issues, :merge_requests]
before_action :group_merge_requests, only: [:merge_requests]
before_action :event_filter, only: [:activity] before_action :event_filter, only: [:activity]
before_action :user_actions, only: [:show, :subgroups] before_action :user_actions, only: [:show, :subgroups]
......
...@@ -40,9 +40,9 @@ class Projects::Clusters::GcpController < Projects::ApplicationController ...@@ -40,9 +40,9 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
def verify_billing def verify_billing
case google_project_billing_status case google_project_billing_status
when nil when nil
flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') flash.now[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
when false when false
flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" } flash.now[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
when true when true
return return
end end
......
...@@ -26,33 +26,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -26,33 +26,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project_member = @project.project_members.new @project_member = @project.project_members.new
end end
def update
@project_member = @project.members_and_requesters.find(params[:id])
.present(current_user: current_user)
return render_403 unless can?(current_user, :update_project_member, @project_member)
old_access_level = @project_member.human_access
if @project_member.update_attributes(member_params)
log_audit_event(@project_member, action: :update, old_access_level: old_access_level)
end
end
def resend_invite
redirect_path = project_project_members_path(@project)
@project_member = @project.project_members.find(params[:id])
if @project_member.invite?
@project_member.resend_invite
redirect_to redirect_path, notice: 'The invitation was successfully resent.'
else
redirect_to redirect_path, alert: 'The invitation has already been accepted.'
end
end
def import def import
@projects = current_user.authorized_projects.order_id_desc @projects = current_user.authorized_projects.order_id_desc
end end
...@@ -71,12 +44,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -71,12 +44,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
notice: notice) notice: notice)
end end
protected
def member_params
params.require(:project_member).permit(:user_id, :access_level, :expires_at)
end
# MembershipActions concern # MembershipActions concern
alias_method :membershipable, :project alias_method :membershipable, :project
end end
module Projects
module Prometheus
class MetricsController < Projects::ApplicationController
before_action :authorize_admin_project!
def active_common
respond_to do |format|
format.json do
matched_metrics = prometheus_service.matched_metrics || {}
if matched_metrics.any?
render json: matched_metrics
else
head :no_content
end
end
end
end
private
def prometheus_service
@prometheus_service ||= project.find_or_initialize_service('prometheus')
end
end
end
end
class Projects::PrometheusController < Projects::ApplicationController
before_action :authorize_read_project!
before_action :require_prometheus_metrics!
def active_metrics
respond_to do |format|
format.json do
matched_metrics = project.prometheus_service.matched_metrics || {}
if matched_metrics.any?
render json: matched_metrics
else
head :no_content
end
end
end
end
private
def require_prometheus_metrics!
render_404 unless project.prometheus_service.present?
end
end
...@@ -39,7 +39,7 @@ class LabelsFinder < UnionFinder ...@@ -39,7 +39,7 @@ class LabelsFinder < UnionFinder
end end
end end
elsif only_group_labels? elsif only_group_labels?
label_ids << Label.where(group_id: group.id) label_ids << Label.where(group_id: group_ids)
else else
label_ids << Label.where(group_id: projects.group_ids) label_ids << Label.where(group_id: projects.group_ids)
label_ids << Label.where(project_id: projects.select(:id)) label_ids << Label.where(project_id: projects.select(:id))
...@@ -59,10 +59,11 @@ class LabelsFinder < UnionFinder ...@@ -59,10 +59,11 @@ class LabelsFinder < UnionFinder
items.where(title: title) items.where(title: title)
end end
def group def group_ids
strong_memoize(:group) do strong_memoize(:group_ids) do
group = Group.find(params[:group_id]) group = Group.find(params[:group_id])
authorized_to_read_labels?(group) && group groups = params[:include_ancestor_groups].present? ? group.self_and_ancestors : [group]
groups_user_can_read_labels(groups).map(&:id)
end end
end end
...@@ -120,4 +121,10 @@ class LabelsFinder < UnionFinder ...@@ -120,4 +121,10 @@ class LabelsFinder < UnionFinder
Ability.allowed?(current_user, :read_label, label_parent) Ability.allowed?(current_user, :read_label, label_parent)
end end
def groups_user_can_read_labels(groups)
DeclarativePolicy.user_scope do
groups.select { |group| authorized_to_read_labels?(group) }
end
end
end end
...@@ -21,6 +21,20 @@ module GroupsHelper ...@@ -21,6 +21,20 @@ module GroupsHelper
can?(current_user, :change_share_with_group_lock, group) can?(current_user, :change_share_with_group_lock, group)
end end
def group_issues_count(state:)
IssuesFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
.execute
.count
end
def group_merge_requests_count(state:)
MergeRequestsFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
.execute
.count
end
def group_icon(group, options = {}) def group_icon(group, options = {})
img_path = group_icon_url(group, options) img_path = group_icon_url(group, options)
image_tag img_path, options image_tag img_path, options
...@@ -85,10 +99,6 @@ module GroupsHelper ...@@ -85,10 +99,6 @@ module GroupsHelper
end end
end end
def group_issues(group)
IssuesFinder.new(current_user, group_id: group.id).execute
end
def remove_group_message(group) def remove_group_message(group)
_("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
{ group_name: group.name } { group_name: group.name }
......
class ChatName < ActiveRecord::Base class ChatName < ActiveRecord::Base
LAST_USED_AT_INTERVAL = 1.hour
belongs_to :service belongs_to :service
belongs_to :user belongs_to :user
...@@ -9,4 +11,23 @@ class ChatName < ActiveRecord::Base ...@@ -9,4 +11,23 @@ class ChatName < ActiveRecord::Base
validates :user_id, uniqueness: { scope: [:service_id] } validates :user_id, uniqueness: { scope: [:service_id] }
validates :chat_id, uniqueness: { scope: [:service_id, :team_id] } validates :chat_id, uniqueness: { scope: [:service_id, :team_id] }
# Updates the "last_used_timestamp" but only if it wasn't already updated
# recently.
#
# The throttling this method uses is put in place to ensure that high chat
# traffic doesn't result in many UPDATE queries being performed.
def update_last_used_at
return unless update_last_used_at?
obtained = Gitlab::ExclusiveLease
.new("chat_name/last_used_at/#{id}", timeout: LAST_USED_AT_INTERVAL.to_i)
.try_obtain
touch(:last_used_at) if obtained
end
def update_last_used_at?
last_used_at.nil? || last_used_at > LAST_USED_AT_INTERVAL.ago
end
end end
...@@ -6,7 +6,10 @@ module Ci ...@@ -6,7 +6,10 @@ module Ci
belongs_to :group belongs_to :group
validates :key, uniqueness: { scope: :group_id } validates :key, uniqueness: {
scope: :group_id,
message: "(%{value}) has already been taken"
}
scope :unprotected, -> { where(protected: false) } scope :unprotected, -> { where(protected: false) }
end end
......
...@@ -7,7 +7,10 @@ module Ci ...@@ -7,7 +7,10 @@ module Ci
belongs_to :project belongs_to :project
validates :key, uniqueness: { scope: [:project_id, :environment_scope] } validates :key, uniqueness: {
scope: [:project_id, :environment_scope],
message: "(%{value}) has already been taken"
}
scope :unprotected, -> { where(protected: false) } scope :unprotected, -> { where(protected: false) }
end end
......
...@@ -8,6 +8,6 @@ module AccessRequestable ...@@ -8,6 +8,6 @@ module AccessRequestable
extend ActiveSupport::Concern extend ActiveSupport::Concern
def request_access(user) def request_access(user)
Members::RequestAccessService.new(self, user).execute Members::RequestAccessService.new(user).execute(self)
end end
end end
...@@ -139,18 +139,25 @@ class Member < ActiveRecord::Base ...@@ -139,18 +139,25 @@ class Member < ActiveRecord::Base
member.attributes = { member.attributes = {
created_by: member.created_by || current_user, created_by: member.created_by || current_user,
access_level: access_level, access_level: access_level,
expires_at: expires_at, expires_at: expires_at
}
## EE-only START
member.attributes = {
skip_notification: ldap, skip_notification: ldap,
ldap: ldap ldap: ldap
} }
## EE-only END
if member.request? if member.request?
::Members::ApproveAccessRequestService.new( ::Members::ApproveAccessRequestService.new(
source,
current_user, current_user,
id: member.id,
access_level: access_level access_level: access_level
).execute(force: ldap) ).execute(
member,
skip_authorization: ldap,
skip_log_audit_event: ldap
)
else else
member.save member.save
end end
......
...@@ -69,16 +69,16 @@ class PrometheusService < MonitoringService ...@@ -69,16 +69,16 @@ class PrometheusService < MonitoringService
client.ping client.ping
{ success: true, result: 'Checked API endpoint' } { success: true, result: 'Checked API endpoint' }
rescue Gitlab::PrometheusError => err rescue Gitlab::PrometheusClient::Error => err
{ success: false, result: err } { success: false, result: err }
end end
def environment_metrics(environment) def environment_metrics(environment)
with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &method(:rename_data_to_metrics)) with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &rename_field(:data, :metrics))
end end
def deployment_metrics(deployment) def deployment_metrics(deployment)
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &method(:rename_data_to_metrics)) metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &rename_field(:data, :metrics))
metrics&.merge(deployment_time: deployment.created_at.to_i) || {} metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
end end
...@@ -107,7 +107,7 @@ class PrometheusService < MonitoringService ...@@ -107,7 +107,7 @@ class PrometheusService < MonitoringService
data: data, data: data,
last_update: Time.now.utc last_update: Time.now.utc
} }
rescue Gitlab::PrometheusError => err rescue Gitlab::PrometheusClient::Error => err
{ success: false, result: err.message } { success: false, result: err.message }
end end
...@@ -116,10 +116,10 @@ class PrometheusService < MonitoringService ...@@ -116,10 +116,10 @@ class PrometheusService < MonitoringService
Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url)) Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url))
else else
cluster = cluster_with_prometheus(environment_id) cluster = cluster_with_prometheus(environment_id)
raise Gitlab::PrometheusError, "couldn't find cluster with Prometheus installed" unless cluster raise Gitlab::PrometheusClient::Error, "couldn't find cluster with Prometheus installed" unless cluster
rest_client = client_from_cluster(cluster) rest_client = client_from_cluster(cluster)
raise Gitlab::PrometheusError, "couldn't create proxy Prometheus client" unless rest_client raise Gitlab::PrometheusClient::Error, "couldn't create proxy Prometheus client" unless rest_client
Gitlab::PrometheusClient.new(rest_client) Gitlab::PrometheusClient.new(rest_client)
end end
...@@ -152,10 +152,12 @@ class PrometheusService < MonitoringService ...@@ -152,10 +152,12 @@ class PrometheusService < MonitoringService
cluster.application_prometheus.proxy_client cluster.application_prometheus.proxy_client
end end
def rename_data_to_metrics(metrics) def rename_field(old_field, new_field)
metrics[:metrics] = metrics.delete :data -> (metrics) do
metrics[new_field] = metrics.delete(old_field)
metrics metrics
end end
end
def synchronize_service_state! def synchronize_service_state!
self.active = prometheus_installed? || manual_configuration? self.active = prometheus_installed? || manual_configuration?
......
...@@ -30,10 +30,10 @@ class SlashCommandsService < Service ...@@ -30,10 +30,10 @@ class SlashCommandsService < Service
def trigger(params) def trigger(params)
return unless valid_token?(params[:token]) return unless valid_token?(params[:token])
user = find_chat_user(params) chat_user = find_chat_user(params)
if user if chat_user&.user
Gitlab::SlashCommands::Command.new(project, user, params).execute Gitlab::SlashCommands::Command.new(project, chat_user, params).execute
else else
url = authorize_chat_name_url(params) url = authorize_chat_name_url(params)
Gitlab::SlashCommands::Presenters::Access.new(url).authorize Gitlab::SlashCommands::Presenters::Access.new(url).authorize
......
...@@ -9,10 +9,9 @@ class Tree ...@@ -9,10 +9,9 @@ class Tree
@repository = repository @repository = repository
@sha = sha @sha = sha
@path = path @path = path
@recursive = recursive
git_repo = @repository.raw_repository git_repo = @repository.raw_repository
@entries = get_entries(git_repo, @sha, @path, recursive: @recursive) @entries = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive)
end end
def readme def readme
...@@ -58,21 +57,4 @@ class Tree ...@@ -58,21 +57,4 @@ class Tree
def sorted_entries def sorted_entries
trees + blobs + submodules trees + blobs + submodules
end end
private
def get_entries(git_repo, sha, path, recursive: false)
current_path_entries = Gitlab::Git::Tree.where(git_repo, sha, path)
ordered_entries = []
current_path_entries.each do |entry|
ordered_entries << entry
if recursive && entry.dir?
ordered_entries.concat(get_entries(git_repo, sha, entry.path, recursive: true))
end
end
ordered_entries
end
end end
...@@ -9,8 +9,8 @@ module ChatNames ...@@ -9,8 +9,8 @@ module ChatNames
chat_name = find_chat_name chat_name = find_chat_name
return unless chat_name return unless chat_name
chat_name.touch(:last_used_at) chat_name.update_last_used_at
chat_name.user chat_name
end end
private private
......
...@@ -79,8 +79,12 @@ class IssuableBaseService < BaseService ...@@ -79,8 +79,12 @@ class IssuableBaseService < BaseService
return unless labels return unless labels
params[:label_ids] = labels.split(",").map do |label_name| params[:label_ids] = labels.split(",").map do |label_name|
service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip) label = Labels::FindOrCreateService.new(
label = service.execute current_user,
parent,
title: label_name.strip,
available_labels: available_labels
).execute
label.try(:id) label.try(:id)
end.compact end.compact
...@@ -104,7 +108,7 @@ class IssuableBaseService < BaseService ...@@ -104,7 +108,7 @@ class IssuableBaseService < BaseService
end end
def available_labels def available_labels
LabelsFinder.new(current_user, project_id: @project.id).execute @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end end
def merge_quick_actions_into_params!(issuable) def merge_quick_actions_into_params!(issuable)
...@@ -305,4 +309,8 @@ class IssuableBaseService < BaseService ...@@ -305,4 +309,8 @@ class IssuableBaseService < BaseService
def update_project_counter_caches?(issuable) def update_project_counter_caches?(issuable)
issuable.state_changed? issuable.state_changed?
end end
def parent
project
end
end end
module Labels module Labels
class FindOrCreateService class FindOrCreateService
def initialize(current_user, project, params = {}) def initialize(current_user, parent, params = {})
@current_user = current_user @current_user = current_user
@project = project @parent = parent
@available_labels = params.delete(:available_labels)
@params = params.dup.with_indifferent_access @params = params.dup.with_indifferent_access
end end
...@@ -13,12 +14,13 @@ module Labels ...@@ -13,12 +14,13 @@ module Labels
private private
attr_reader :current_user, :project, :params, :skip_authorization attr_reader :current_user, :parent, :params, :skip_authorization
def available_labels def available_labels
@available_labels ||= LabelsFinder.new( @available_labels ||= LabelsFinder.new(
current_user, current_user,
project_id: project.id "#{parent_type}_id".to_sym => parent.id,
only_group_labels: parent_is_group?
).execute(skip_authorization: skip_authorization) ).execute(skip_authorization: skip_authorization)
end end
...@@ -27,8 +29,8 @@ module Labels ...@@ -27,8 +29,8 @@ module Labels
def find_or_create_label def find_or_create_label
new_label = available_labels.find_by(title: title) new_label = available_labels.find_by(title: title)
if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, project)) if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, parent))
new_label = Labels::CreateService.new(params).execute(project: project) new_label = Labels::CreateService.new(params).execute(parent_type.to_sym => parent)
end end
new_label new_label
...@@ -37,5 +39,13 @@ module Labels ...@@ -37,5 +39,13 @@ module Labels
def title def title
params[:title] || params[:name] params[:title] || params[:name]
end end
def parent_type
parent.model_name.param_key
end
def parent_is_group?
parent_type == "group"
end
end end
end end
module Members module Members
class ApproveAccessRequestService < BaseService class ApproveAccessRequestService < Members::BaseService
include MembersHelper prepend EE::Members::ApproveAccessRequestService
attr_accessor :source def execute(access_requester, skip_authorization: false, skip_log_audit_event: false)
raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_update_access_requester?(access_requester)
# source - The source object that respond to `#requesters` (i.g. project or group)
# current_user - The user that performs the access request approval
# params - A hash of parameters
# :user_id - User ID used to retrieve the access requester
# :id - Member ID used to retrieve the access requester
# :access_level - Optional access level set when the request is accepted
def initialize(source, current_user, params = {})
@source = source
@current_user = current_user
@params = params.slice(:user_id, :id, :access_level)
end
# opts - A hash of options
# :force - Bypass permission check: current_user can be nil in that case
def execute(opts = {})
condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] }
access_requester = source.requesters.find_by!(condition)
raise Gitlab::Access::AccessDeniedError unless can_update_access_requester?(access_requester, opts)
access_requester.access_level = params[:access_level] if params[:access_level] access_requester.access_level = params[:access_level] if params[:access_level]
access_requester.accept_request access_requester.accept_request
after_execute(member: access_requester, skip_log_audit_event: skip_log_audit_event)
access_requester access_requester
end end
private private
def can_update_access_requester?(access_requester, opts = {}) def can_update_access_requester?(access_requester)
access_requester && (
opts[:force] ||
can?(current_user, update_member_permission(access_requester), access_requester) can?(current_user, update_member_permission(access_requester), access_requester)
)
end
def update_member_permission(member)
case member
when GroupMember
:update_group_member
when ProjectMember
:update_project_member
end
end end
end end
end end
module Members
class AuthorizedDestroyService < BaseService
attr_accessor :member, :user
def initialize(member, user = nil)
@member, @user = member, user
end
def execute
return false if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
Member.transaction do
unassign_issues_and_merge_requests(member) unless member.invite?
member.notification_setting&.destroy
member.destroy
end
if member.request? && member.user != user
notification_service.decline_access_request(member)
end
member
end
private
def unassign_issues_and_merge_requests(member)
if member.is_a?(GroupMember)
issues = Issue.unscoped.select(1)
.joins(:project)
.where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id)
# DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
IssueAssignee.unscoped
.where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
.delete_all
MergeRequestsFinder.new(user, group_id: member.source_id, assignee_id: member.user_id)
.execute
.update_all(assignee_id: nil)
else
project = member.source
# SELECT 1 FROM issues WHERE issues.id = issue_assignees.issue_id AND issues.project_id = X
issues = Issue.unscoped.select(1)
.where('issues.id = issue_assignees.issue_id')
.where(project_id: project.id)
# DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
IssueAssignee.unscoped
.where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
.delete_all
project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil)
end
member.user.invalidate_cache_counts
end
end
end
module Members
class BaseService < ::BaseService
# current_user - The user that performs the action
# params - A hash of parameters
def initialize(current_user = nil, params = {})
@current_user = current_user
@params = params
end
def after_execute(args)
# overriden in EE::Members modules
end
private
def update_member_permission(member)
case member
when GroupMember
:update_group_member
when ProjectMember
:update_project_member
else
raise "Unknown member type: #{member}!"
end
end
def override_member_permission(member)
case member
when GroupMember
:override_group_member
when ProjectMember
:override_project_member
else
raise "Unknown member type: #{member}!"
end
end
def action_member_permission(action, member)
case action
when :update
update_member_permission(member)
when :override
override_member_permission(member)
else
raise "Unknown action '#{action}' on #{member}!"
end
end
end
end
module Members module Members
class CreateService < BaseService class CreateService < Members::BaseService
DEFAULT_LIMIT = 100 prepend EE::Members::CreateService
def initialize(source, current_user, params = {}) DEFAULT_LIMIT = 100
@source = source
@current_user = current_user
@params = params
@error = nil
end
def execute def execute(source)
return error('No users specified.') if params[:user_ids].blank? return error('No users specified.') if params[:user_ids].blank?
user_ids = params[:user_ids].split(',').uniq user_ids = params[:user_ids].split(',').uniq
...@@ -17,17 +12,14 @@ module Members ...@@ -17,17 +12,14 @@ module Members
return error("Too many users specified (limit is #{user_limit})") if return error("Too many users specified (limit is #{user_limit})") if
user_limit && user_ids.size > user_limit user_limit && user_ids.size > user_limit
members = @source.add_users( members = source.add_users(
user_ids, user_ids,
params[:access_level], params[:access_level],
expires_at: params[:expires_at], expires_at: params[:expires_at],
current_user: current_user current_user: current_user
) )
members.compact.each do |member| members.each { |member| after_execute(member: member) }
AuditEventService.new(@current_user, @source, action: :create)
.for_member(member).security_event
end
success success
end end
......
module Members module Members
class DestroyService < BaseService class DestroyService < Members::BaseService
include MembersHelper prepend EE::Members::DestroyService
attr_accessor :source def execute(member, skip_authorization: false)
raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_destroy_member?(member)
ALLOWED_SCOPES = %i[members requesters all].freeze return member if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
def initialize(source, current_user, params = {}) Member.transaction do
@source = source unassign_issues_and_merge_requests(member) unless member.invite?
@current_user = current_user member.notification_setting&.destroy
@params = params
end
def execute(scope = :members)
raise "scope :#{scope} is not allowed!" unless ALLOWED_SCOPES.include?(scope)
member = find_member!(scope)
raise Gitlab::Access::AccessDeniedError unless can_destroy_member?(member) member.destroy
end
AuthorizedDestroyService.new(member, current_user).execute if member.request? && member.user != current_user
notification_service.decline_access_request(member)
end
AuditEventService.new(@current_user, @source, action: :destroy) after_execute(member: member)
.for_member(member).security_event
member member
end end
private private
def find_member!(scope)
condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] }
case scope
when :all
source.members.find_by(condition) ||
source.requesters.find_by!(condition)
else
source.public_send(scope).find_by!(condition) # rubocop:disable GitlabSecurity/PublicSend
end
end
def can_destroy_member?(member) def can_destroy_member?(member)
member && can?(current_user, destroy_member_permission(member), member) can?(current_user, destroy_member_permission(member), member)
end end
def destroy_member_permission(member) def destroy_member_permission(member)
...@@ -50,7 +35,42 @@ module Members ...@@ -50,7 +35,42 @@ module Members
:destroy_group_member :destroy_group_member
when ProjectMember when ProjectMember
:destroy_project_member :destroy_project_member
else
raise "Unknown member type: #{member}!"
end end
end end
def unassign_issues_and_merge_requests(member)
if member.is_a?(GroupMember)
issues = Issue.unscoped.select(1)
.joins(:project)
.where('issues.id = issue_assignees.issue_id AND projects.namespace_id = ?', member.source_id)
# DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
IssueAssignee.unscoped
.where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
.delete_all
MergeRequestsFinder.new(current_user, group_id: member.source_id, assignee_id: member.user_id)
.execute
.update_all(assignee_id: nil)
else
project = member.source
# SELECT 1 FROM issues WHERE issues.id = issue_assignees.issue_id AND issues.project_id = X
issues = Issue.unscoped.select(1)
.where('issues.id = issue_assignees.issue_id')
.where(project_id: project.id)
# DELETE FROM issue_assignees WHERE user_id = X AND EXISTS (...)
IssueAssignee.unscoped
.where('user_id = :user_id AND EXISTS (:sub)', user_id: member.user_id, sub: issues)
.delete_all
project.merge_requests.opened.assigned_to(member.user).update_all(assignee_id: nil)
end
member.user.invalidate_cache_counts
end
end end
end end
module Members module Members
class RequestAccessService < BaseService class RequestAccessService < Members::BaseService
attr_accessor :source def execute(source)
def initialize(source, current_user)
@source = source
@current_user = current_user
end
def execute
raise Gitlab::Access::AccessDeniedError unless can_request_access?(source) raise Gitlab::Access::AccessDeniedError unless can_request_access?(source)
source.members.create( source.members.create(
...@@ -19,7 +12,7 @@ module Members ...@@ -19,7 +12,7 @@ module Members
private private
def can_request_access?(source) def can_request_access?(source)
source && can?(current_user, :request_access, source) can?(current_user, :request_access, source)
end end
end end
end end
module Members
class UpdateService < Members::BaseService
prepend EE::Members::UpdateService
# returns the updated member
def execute(member, permission: :update)
raise Gitlab::Access::AccessDeniedError unless can?(current_user, action_member_permission(permission, member), member)
old_access_level = member.human_access
if member.update_attributes(params)
after_execute(action: permission, old_access_level: old_access_level, member: member)
end
member
end
end
end
...@@ -24,8 +24,7 @@ module RecordsUploads ...@@ -24,8 +24,7 @@ module RecordsUploads
uploads.where(path: upload_path).delete_all uploads.where(path: upload_path).delete_all
upload.destroy! if upload upload.destroy! if upload
self.upload = build_upload self.upload = build_upload.tap(&:save!)
upload.save!
end end
end end
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
# - 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)
return if record.errors.include?(:"#{attribute}.key")
if options[:scope] if options[:scope]
scoped = value.group_by do |variable| scoped = value.group_by do |variable|
Array(options[:scope]).map { |attr| variable.send(attr) } # rubocop:disable GitlabSecurity/PublicSend Array(options[:scope]).map { |attr| variable.send(attr) } # rubocop:disable GitlabSecurity/PublicSend
......
...@@ -35,9 +35,8 @@ ...@@ -35,9 +35,8 @@
method: :put, class: 'btn btn-default', method: :put, class: 'btn btn-default',
data: { confirm: _("Are you sure you want to reset registration token?") } data: { confirm: _("Are you sure you want to reset registration token?") }
= render partial: 'ci/runner/how_to_setup_runner', = render partial: 'ci/runner/how_to_setup_shared_runner',
locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token }
type: 'shared' }
.append-bottom-20.clearfix .append-bottom-20.clearfix
.pull-left .pull-left
......
- link = link_to _("GitLab Runner section"), 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank' - link = link_to _("GitLab Runner section"), 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'
.bs-callout.help-callout .append-bottom-10
%h4= _("How to setup a #{type} Runner for a new project") %h4= _("Setup a #{type} Runner manually")
%ol %ol
%li %li
= _("Install a Runner compatible with GitLab CI") = _("Install a Runner compatible with GitLab CI")
= (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe = (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe
......
.bs-callout.help-callout
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: registration_token, type: 'shared' }
.bs-callout.help-callout
.append-bottom-10
%h4= _('Setup a specific Runner automatically')
%p
- link_to_help_page = link_to(_('Learn more about Kubernetes'),
help_page_path('user/project/clusters/index'),
target: '_blank',
rel: 'noopener noreferrer')
= _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page }
%ol
%li
= _('Click the button below to begin the install process by navigating to the Kubernetes page')
%li
= _('Select an existing Kubernetes cluster or create a new one')
%li
= _('From the Kubernetes cluster details view, install Runner from the applications list')
= link_to _('Install Runner on Kubernetes'),
project_clusters_path(@project),
class: 'btn btn-info'
%hr
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: registration_token, type: 'specific' }
:plain
var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}');
$("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@group_member)}"));
- page_title "Issues" - page_title "Issues"
- group_issues_exists = group_issues(@group).exists?
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
...@@ -7,7 +6,9 @@ ...@@ -7,7 +6,9 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'issues' = webpack_bundle_tag 'issues'
- if group_issues_exists - if group_issues_count(state: 'all').zero?
= render 'shared/empty_states/issues', project_select_button: true
- else
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .nav-controls
...@@ -20,5 +21,3 @@ ...@@ -20,5 +21,3 @@
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
= render 'shared/issues' = render 'shared/issues'
- else
= render 'shared/empty_states/issues', project_select_button: true
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
- if @group_merge_requests.empty? - if group_merge_requests_count(state: 'all').zero?
= render 'shared/empty_states/merge_requests', project_select_button: true = render 'shared/empty_states/merge_requests', project_select_button: true
- else - else
.top-area .top-area
......
- issues_count = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute.count - issues_count = group_issues_count(state: 'opened')
- merge_requests_count = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute.count - merge_requests_count = group_merge_requests_count(state: 'opened')
- issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index'] - issues_sub_menu_items = ['groups#issues', 'labels#index', 'milestones#index']
- if @group.feature_available?(:group_issue_boards) - if @group.feature_available?(:group_issue_boards)
......
...@@ -27,6 +27,3 @@ ...@@ -27,6 +27,3 @@
- unless can?(current_user, :push_code, @project) - unless can?(current_user, :push_code, @project)
.inline.prepend-left-10 .inline.prepend-left-10
= commit_in_fork_help = commit_in_fork_help
- content_for :page_specific_javascripts do
= webpack_bundle_tag('blob')
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.col-xs-12 .col-xs-12
.text-content .text-content
%h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation') %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation')
- link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page} %p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
.text-center .text-center
......
...@@ -9,4 +9,3 @@ ...@@ -9,4 +9,3 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= webpack_bundle_tag('commit_pipelines')
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
- page_description @commit.description - page_description @commit.description
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= webpack_bundle_tag('diff_notes')
.container-fluid{ class: [limited_container_width, container_class] } .container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box" = render "commit_box"
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.avatar-cell.hidden-xs .avatar-cell.hidden-xs
= author_avatar(commit, size: 36) = author_avatar(commit, size: 36)
.commit-detail .commit-detail.flex-list
.commit-content .commit-content
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title") = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline %span.commit-row-message.visible-xs-inline
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue') = webpack_bundle_tag('common_vue')
= webpack_bundle_tag("environments_folder")
#environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json), #environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json),
"folder-name" => @folder, "folder-name" => @folder,
......
...@@ -31,9 +31,6 @@ ...@@ -31,9 +31,6 @@
#js-vue-mr-widget.mr-widget #js-vue-mr-widget.mr-widget
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'vue_merge_request_widget'
.content-block.content-block-small.emoji-list-container .content-block.content-block-small.emoji-list-container
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true = render 'award_emoji/awards_block', awardable: @merge_request, inline: true
......
- breadcrumb_title "Graph" - breadcrumb_title "Graph"
- page_title "Graph", @ref - page_title "Graph", @ref
- content_for :page_specific_javascripts do
= webpack_bundle_tag('network')
= render "head" = render "head"
%div{ class: container_class } %div{ class: container_class }
.project-network .project-network
......
...@@ -15,6 +15,3 @@ ...@@ -15,6 +15,3 @@
"has-ci" => @repository.gitlab_ci_yml, "has-ci" => @repository.gitlab_ci_yml,
"ci-lint-path" => ci_lint_path, "ci-lint-path" => ci_lint_path,
"reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } } "reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } }
= webpack_bundle_tag('common_vue')
= webpack_bundle_tag('pipelines')
%h3 Shared Runners %h3 Shared Runners
.bs-callout.bs-callout-warning.shared-runners-description .bs-callout.shared-runners-description
- if Gitlab::CurrentSettings.shared_runners_text.present? - if Gitlab::CurrentSettings.shared_runners_text.present?
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :shared_runners_text) = markdown_field(Gitlab::CurrentSettings.current_application_settings, :shared_runners_text)
- else - else
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
on GitLab.com). on GitLab.com).
%hr %hr
- if @project.shared_runners_enabled? - if @project.shared_runners_enabled?
= link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-warning', method: :post do = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-close', method: :post do
Disable shared Runners Disable shared Runners
- else - else
= link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-success', method: :post do = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-success', method: :post do
......
%h3 Specific Runners %h3 Specific Runners
= render partial: 'ci/runner/how_to_setup_runner', = render partial: 'ci/runner/how_to_setup_specific_runner',
locals: { registration_token: @project.runners_token, locals: { registration_token: @project.runners_token }
type: 'specific' }
- if @project_runners.any? - if @project_runners.any?
%h4.underlined-title Runners activated for this project %h4.underlined-title Runners activated for this project
......
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.
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.
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