Merge branch 'master' into 22643-manual-job-page
* master: (200 commits)
Fix issue boards scroll config.
Added multi editor setting on the profile preferences page
fix missing import of timeWeek which would cause errors in prometheus graphs with deployments
Remove downcase from special path helper
Rename db:seed_fu-{pg,mysql} to gitlab:setup-{pg,mysql}
Fix error when viewing diffs without blobs
Moves prettier to dev dependency
Eager load event target authors whenever possible
Do not run ee_compat_check on security branches
Include integration tests in CE/EE testing documentation
41054-Disallow creation of new Kubernetes integrations
Resolve "Resizable file list and commit panel"
Make tooltip placement bottom by default as per design guidelines
Fix groups list icon, timestamp alignment and row height
Avoid leaving a push event empty if payload cannot be created
Move git operations for UpdateRemoteMirrorService into Gitlab::Git
Move delete_remote_branches from Gitlab::Shell to Gitlab::Git::Repository
Move push_remote_branches from Gitlab::Shell to Gitlab::Git::Repository
Update Kubernetes service documentation
fix issue #37843
...
This commit is contained in:
commit
a29a91f02d
|
|
@ -431,6 +431,7 @@ ee_compat_check:
|
|||
- master
|
||||
- tags
|
||||
- /^[\d-]+-stable(-ee)?/
|
||||
- /^security-/
|
||||
- branches@gitlab-org/gitlab-ee
|
||||
- branches@gitlab/gitlab-ee
|
||||
retry: 0
|
||||
|
|
@ -508,7 +509,7 @@ db:rollback-mysql:
|
|||
<<: *db-rollback
|
||||
<<: *use-mysql
|
||||
|
||||
.db-seed_fu: &db-seed_fu
|
||||
.gitlab-setup: &gitlab-setup
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs-and-qa
|
||||
<<: *pull-cache
|
||||
|
|
@ -517,22 +518,24 @@ db:rollback-mysql:
|
|||
SIZE: "1"
|
||||
SETUP_DB: "false"
|
||||
CREATE_DB_USER: "true"
|
||||
FIXTURE_PATH: db/fixtures/development
|
||||
script:
|
||||
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
|
||||
/home/git/repositories/gitlab-org/gitlab-test.git
|
||||
- bundle exec rake db:setup db:seed_fu
|
||||
- scripts/gitaly-test-spawn
|
||||
- force=yes bundle exec rake gitlab:setup
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expire_in: 1d
|
||||
paths:
|
||||
- log/development.log
|
||||
|
||||
db:seed_fu-pg:
|
||||
<<: *db-seed_fu
|
||||
gitlab:setup-pg:
|
||||
<<: *gitlab-setup
|
||||
<<: *use-pg
|
||||
|
||||
db:seed_fu-mysql:
|
||||
<<: *db-seed_fu
|
||||
gitlab:setup-mysql:
|
||||
<<: *gitlab-setup
|
||||
<<: *use-mysql
|
||||
|
||||
# Frontend-related jobs
|
||||
|
|
@ -600,6 +603,14 @@ codequality:
|
|||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
|
||||
sast:
|
||||
image: registry.gitlab.com/gitlab-org/gl-sast:latest
|
||||
before_script: []
|
||||
script:
|
||||
- /app/bin/run .
|
||||
artifacts:
|
||||
paths: [gl-sast-report.json]
|
||||
|
||||
qa:internal:
|
||||
<<: *dedicated-runner
|
||||
<<: *except-docs
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ inherit_gem:
|
|||
- rubocop-default.yml
|
||||
|
||||
inherit_from: .rubocop_todo.yml
|
||||
require: ./rubocop/rubocop
|
||||
|
||||
AllCops:
|
||||
TargetRailsVersion: 4.2
|
||||
|
|
@ -24,8 +25,10 @@ Gitlab/ModuleWithInstanceVariables:
|
|||
Exclude:
|
||||
# We ignore Rails helpers right now because it's hard to workaround it
|
||||
- app/helpers/**/*_helper.rb
|
||||
- ee/app/helpers/**/*_helper.rb
|
||||
# We ignore Rails mailers right now because it's hard to workaround it
|
||||
- app/mailers/emails/**/*.rb
|
||||
- ee/**/emails/**/*.rb
|
||||
# We ignore spec helpers because it usually doesn't matter
|
||||
- spec/support/**/*.rb
|
||||
- features/steps/**/*.rb
|
||||
|
|
|
|||
29
CHANGELOG.md
29
CHANGELOG.md
|
|
@ -2,6 +2,35 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 10.3.3 (2018-01-02)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
||||
- Fix links to old commits in merge request comments.
|
||||
- Fix 404 errors after a user edits an issue description and solves the reCAPTCHA.
|
||||
- Gracefully handle orphaned write deploy keys in /internal/post_receive.
|
||||
|
||||
|
||||
## 10.3.2 (2017-12-28)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
||||
- Fix migration for removing orphaned issues.moved_to_id values in MySQL and PostgreSQL.
|
||||
|
||||
|
||||
## 10.3.1 (2017-12-27)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
||||
- Don't link LFS objects to a project when unlinking forks when they were already linked. !16006
|
||||
- Execute project hooks and services after commit when moving an issue.
|
||||
- Fix Error 500s with anonymous clones for a project that has moved.
|
||||
|
||||
### Changed (1 change)
|
||||
|
||||
- Reduce the number of buckets in gitlab_cache_operation_duration_seconds metric. !15881
|
||||
|
||||
|
||||
## 10.3.0 (2017-12-22)
|
||||
|
||||
### Security (1 change, 1 of them is from the community)
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@ the feature you contribute through all of these steps.
|
|||
|
||||
1. Description explaining the relevancy (see following item)
|
||||
1. Working and clean code that is commented where needed
|
||||
1. [Unit and system tests][testing] that pass on the CI server
|
||||
1. [Unit, integration, and system tests][testing] that pass on the CI server
|
||||
1. Performance/scalability implications have been considered, addressed, and tested
|
||||
1. [Documented][doc-styleguide] in the `/doc` directory
|
||||
1. [Changelog entry added][changelog], if necessary
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.60.0
|
||||
0.65.0
|
||||
|
|
|
|||
6
Gemfile
6
Gemfile
|
|
@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.0'
|
|||
gem 'default_value_for', '~> 3.0.0'
|
||||
|
||||
# Supported DBs
|
||||
gem 'mysql2', '~> 0.4.5', group: :mysql
|
||||
gem 'mysql2', '~> 0.4.10', group: :mysql
|
||||
gem 'pg', '~> 0.18.2', group: :postgres
|
||||
|
||||
gem 'rugged', '~> 0.26.0'
|
||||
|
|
@ -283,7 +283,7 @@ group :metrics do
|
|||
gem 'influxdb', '~> 0.2', require: false
|
||||
|
||||
# Prometheus
|
||||
gem 'prometheus-client-mmap', '~> 0.7.0.beta43'
|
||||
gem 'prometheus-client-mmap', '~> 0.7.0.beta44'
|
||||
gem 'raindrops', '~> 0.18'
|
||||
end
|
||||
|
||||
|
|
@ -402,7 +402,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.61.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.64.0', require: 'gitaly'
|
||||
|
||||
gem 'toml-rb', '~> 0.3.15', require: false
|
||||
|
||||
|
|
|
|||
18
Gemfile.lock
18
Gemfile.lock
|
|
@ -284,7 +284,7 @@ GEM
|
|||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
gitaly-proto (0.61.0)
|
||||
gitaly-proto (0.64.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.0)
|
||||
github-linguist (4.7.6)
|
||||
|
|
@ -505,7 +505,7 @@ GEM
|
|||
mustermann (1.0.0)
|
||||
mustermann-grape (1.0.0)
|
||||
mustermann (~> 1.0.0)
|
||||
mysql2 (0.4.5)
|
||||
mysql2 (0.4.10)
|
||||
net-ldap (0.16.0)
|
||||
net-ssh (4.1.0)
|
||||
netrc (0.11.0)
|
||||
|
|
@ -634,7 +634,7 @@ GEM
|
|||
parser
|
||||
unparser
|
||||
procto (0.0.3)
|
||||
prometheus-client-mmap (0.7.0.beta43)
|
||||
prometheus-client-mmap (0.7.0.beta44)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
|
|
@ -708,7 +708,7 @@ GEM
|
|||
json
|
||||
recursive-open-struct (1.0.0)
|
||||
redcarpet (3.4.0)
|
||||
redis (3.3.3)
|
||||
redis (3.3.5)
|
||||
redis-actionpack (5.0.2)
|
||||
actionpack (>= 4.0, < 6)
|
||||
redis-rack (>= 1, < 3)
|
||||
|
|
@ -839,11 +839,11 @@ GEM
|
|||
rack
|
||||
shoulda-matchers (3.1.2)
|
||||
activesupport (>= 4.0.0)
|
||||
sidekiq (5.0.4)
|
||||
sidekiq (5.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (~> 3.3, >= 3.3.3)
|
||||
redis (>= 3.3.4, < 5)
|
||||
sidekiq-cron (0.6.0)
|
||||
rufus-scheduler (>= 3.3.0)
|
||||
sidekiq (>= 4.2.1)
|
||||
|
|
@ -1046,7 +1046,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.2.0)
|
||||
gitaly-proto (~> 0.61.0)
|
||||
gitaly-proto (~> 0.64.0)
|
||||
github-linguist (~> 4.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-markup (~> 1.6.2)
|
||||
|
|
@ -1087,7 +1087,7 @@ DEPENDENCIES
|
|||
method_source (~> 0.8)
|
||||
minitest (~> 5.7.0)
|
||||
mousetrap-rails (~> 1.4.6)
|
||||
mysql2 (~> 0.4.5)
|
||||
mysql2 (~> 0.4.10)
|
||||
net-ldap
|
||||
net-ssh (~> 4.1.0)
|
||||
nokogiri (~> 1.8.1)
|
||||
|
|
@ -1122,7 +1122,7 @@ DEPENDENCIES
|
|||
peek-sidekiq (~> 1.0.3)
|
||||
pg (~> 0.18.2)
|
||||
premailer-rails (~> 1.9.7)
|
||||
prometheus-client-mmap (~> 0.7.0.beta43)
|
||||
prometheus-client-mmap (~> 0.7.0.beta44)
|
||||
pry-byebug (~> 3.4.1)
|
||||
pry-rails (~> 0.3.4)
|
||||
rack-attack (~> 4.4.1)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 388 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
|
|
@ -1,10 +1,8 @@
|
|||
/* eslint-disable no-new */
|
||||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
import notebookLab from '../../notebook/index.vue';
|
||||
|
||||
Vue.use(VueResource);
|
||||
|
||||
export default () => {
|
||||
const el = document.getElementById('js-notebook-viewer');
|
||||
|
||||
|
|
@ -50,14 +48,14 @@ export default () => {
|
|||
`,
|
||||
methods: {
|
||||
loadFile() {
|
||||
this.$http.get(el.dataset.endpoint)
|
||||
.then(response => response.json())
|
||||
.then((res) => {
|
||||
this.json = res;
|
||||
axios.get(el.dataset.endpoint)
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.json = data;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.status) {
|
||||
if (e.status !== 200) {
|
||||
this.loadError = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import _ from 'underscore';
|
||||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import Flash from '../flash';
|
||||
import { __ } from '../locale';
|
||||
import FilteredSearchBoards from './filtered_search_boards';
|
||||
|
|
@ -25,8 +24,6 @@ import './components/new_list_dropdown';
|
|||
import './components/modal/index';
|
||||
import '../vue_shared/vue_resource_interceptor';
|
||||
|
||||
Vue.use(VueResource);
|
||||
|
||||
$(() => {
|
||||
const $boardApp = document.getElementById('board-app');
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
|
@ -95,14 +92,13 @@ $(() => {
|
|||
|
||||
Store.disabled = this.disabled;
|
||||
gl.boardService.all()
|
||||
.then(response => response.json())
|
||||
.then((resp) => {
|
||||
resp.forEach((board) => {
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
data.forEach((board) => {
|
||||
const list = Store.addList(board, this.defaultAvatar);
|
||||
|
||||
if (list.type === 'closed') {
|
||||
list.position = Infinity;
|
||||
list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' };
|
||||
} else if (list.type === 'backlog') {
|
||||
list.position = -1;
|
||||
}
|
||||
|
|
@ -113,7 +109,9 @@ $(() => {
|
|||
Store.addBlankState();
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => new Flash('An error occurred. Please try again.'));
|
||||
.catch(() => {
|
||||
Flash('An error occurred while fetching the board lists. Please try again.');
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
updateTokens() {
|
||||
|
|
@ -124,7 +122,7 @@ $(() => {
|
|||
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
|
||||
newIssue.setFetchingState('subscriptions', true);
|
||||
BoardService.getIssueInfo(sidebarInfoEndpoint)
|
||||
.then(res => res.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
newIssue.setFetchingState('subscriptions', false);
|
||||
newIssue.updateData({
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export default {
|
|||
|
||||
// Save the labels
|
||||
gl.boardService.generateDefaultLists()
|
||||
.then(resp => resp.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
data.forEach((listObj) => {
|
||||
const list = Store.findList('title', listObj.title);
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||
scroll: document.querySelectorAll('.boards-list')[0],
|
||||
scroll: true,
|
||||
group: 'issues',
|
||||
disabled: this.disabled,
|
||||
filter: '.board-list-count, .is-disabled',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable comma-dangle, space-before-function-paren, no-new */
|
||||
/* global MilestoneSelect */
|
||||
|
||||
import Vue from 'vue';
|
||||
import Flash from '../../flash';
|
||||
|
|
@ -12,6 +11,7 @@ import './sidebar/remove_issue';
|
|||
import IssuableContext from '../../issuable_context';
|
||||
import LabelsSelect from '../../labels_select';
|
||||
import subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
|
||||
import MilestoneSelect from '../../milestone_select';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ gl.issueBoards.IssuesModal = Vue.extend({
|
|||
page: this.page,
|
||||
per: this.perPage,
|
||||
}))
|
||||
.then(resp => resp.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
if (clearIssues) {
|
||||
this.issues = [];
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class List {
|
|||
|
||||
save () {
|
||||
return gl.boardService.createList(this.label.id)
|
||||
.then(resp => resp.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.id = data.id;
|
||||
this.type = data.list_type;
|
||||
|
|
@ -90,7 +90,7 @@ class List {
|
|||
}
|
||||
|
||||
return gl.boardService.getIssuesForList(this.id, data)
|
||||
.then(resp => resp.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.loading = false;
|
||||
this.issuesSize = data.size;
|
||||
|
|
@ -108,7 +108,7 @@ class List {
|
|||
this.issuesSize += 1;
|
||||
|
||||
return gl.boardService.newIssue(this.id, issue)
|
||||
.then(resp => resp.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
issue.id = data.id;
|
||||
issue.iid = data.iid;
|
||||
|
|
|
|||
|
|
@ -1,82 +1,79 @@
|
|||
/* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, max-len, no-unused-vars */
|
||||
|
||||
import Vue from 'vue';
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
import { mergeUrlParams } from '../../lib/utils/url_utility';
|
||||
|
||||
export default class BoardService {
|
||||
constructor ({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) {
|
||||
this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, {
|
||||
issues: {
|
||||
method: 'GET',
|
||||
url: `${gon.relative_url_root}/-/boards/${boardId}/issues.json`,
|
||||
}
|
||||
});
|
||||
this.lists = Vue.resource(`${listsEndpoint}{/id}`, {}, {
|
||||
generate: {
|
||||
method: 'POST',
|
||||
url: `${listsEndpoint}/generate.json`
|
||||
}
|
||||
});
|
||||
this.issue = Vue.resource(`${gon.relative_url_root}/-/boards/${boardId}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${listsEndpoint}{/id}/issues`, {}, {
|
||||
bulkUpdate: {
|
||||
method: 'POST',
|
||||
url: bulkUpdatePath,
|
||||
constructor({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) {
|
||||
this.boardsEndpoint = boardsEndpoint;
|
||||
this.boardId = boardId;
|
||||
this.listsEndpoint = listsEndpoint;
|
||||
this.listsEndpointGenerate = `${listsEndpoint}/generate.json`;
|
||||
this.bulkUpdatePath = bulkUpdatePath;
|
||||
}
|
||||
|
||||
generateBoardsPath(id) {
|
||||
return `${this.boardsEndpoint}${id ? `/${id}` : ''}.json`;
|
||||
}
|
||||
|
||||
generateIssuesPath(id) {
|
||||
return `${this.listsEndpoint}${id ? `/${id}` : ''}/issues`;
|
||||
}
|
||||
|
||||
static generateIssuePath(boardId, id) {
|
||||
return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
|
||||
}
|
||||
|
||||
all() {
|
||||
return axios.get(this.listsEndpoint);
|
||||
}
|
||||
|
||||
generateDefaultLists() {
|
||||
return axios.post(this.listsEndpointGenerate, {});
|
||||
}
|
||||
|
||||
createList(labelId) {
|
||||
return axios.post(this.listsEndpoint, {
|
||||
list: {
|
||||
label_id: labelId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
all () {
|
||||
return this.lists.get();
|
||||
}
|
||||
|
||||
generateDefaultLists () {
|
||||
return this.lists.generate({});
|
||||
}
|
||||
|
||||
createList (label_id) {
|
||||
return this.lists.save({}, {
|
||||
updateList(id, position) {
|
||||
return axios.put(`${this.listsEndpoint}/${id}`, {
|
||||
list: {
|
||||
label_id
|
||||
}
|
||||
position,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateList (id, position) {
|
||||
return this.lists.update({ id }, {
|
||||
list: {
|
||||
position
|
||||
}
|
||||
});
|
||||
destroyList(id) {
|
||||
return axios.delete(`${this.listsEndpoint}/${id}`);
|
||||
}
|
||||
|
||||
destroyList (id) {
|
||||
return this.lists.delete({ id });
|
||||
}
|
||||
|
||||
getIssuesForList (id, filter = {}) {
|
||||
getIssuesForList(id, filter = {}) {
|
||||
const data = { id };
|
||||
Object.keys(filter).forEach((key) => { data[key] = filter[key]; });
|
||||
|
||||
return this.issues.get(data);
|
||||
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
|
||||
}
|
||||
|
||||
moveIssue (id, from_list_id = null, to_list_id = null, move_before_id = null, move_after_id = null) {
|
||||
return this.issue.update({ id }, {
|
||||
from_list_id,
|
||||
to_list_id,
|
||||
move_before_id,
|
||||
move_after_id,
|
||||
moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
|
||||
return axios.put(BoardService.generateIssuePath(this.boardId, id), {
|
||||
from_list_id: fromListId,
|
||||
to_list_id: toListId,
|
||||
move_before_id: moveBeforeId,
|
||||
move_after_id: moveAfterId,
|
||||
});
|
||||
}
|
||||
|
||||
newIssue (id, issue) {
|
||||
return this.issues.save({ id }, {
|
||||
issue
|
||||
newIssue(id, issue) {
|
||||
return axios.post(this.generateIssuesPath(id), {
|
||||
issue,
|
||||
});
|
||||
}
|
||||
|
||||
getBacklog(data) {
|
||||
return this.boards.issues(data);
|
||||
return axios.get(mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`));
|
||||
}
|
||||
|
||||
bulkUpdate(issueIds, extraData = {}) {
|
||||
|
|
@ -86,15 +83,15 @@ export default class BoardService {
|
|||
}),
|
||||
};
|
||||
|
||||
return this.issues.bulkUpdate(data);
|
||||
return axios.post(this.bulkUpdatePath, data);
|
||||
}
|
||||
|
||||
static getIssueInfo(endpoint) {
|
||||
return Vue.http.get(endpoint);
|
||||
return axios.get(endpoint);
|
||||
}
|
||||
|
||||
static toggleIssueSubscription(endpoint) {
|
||||
return Vue.http.post(endpoint);
|
||||
return axios.post(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import IssuableIndex from './issuable_index';
|
|||
import Milestone from './milestone';
|
||||
import IssuableForm from './issuable_form';
|
||||
import LabelsSelect from './labels_select';
|
||||
/* global MilestoneSelect */
|
||||
import MilestoneSelect from './milestone_select';
|
||||
import NewBranchForm from './new_branch_form';
|
||||
import NotificationsForm from './notifications_form';
|
||||
import notificationsDropdown from './notifications_dropdown';
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class FilteredSearchManager {
|
|||
this.handleInputVisualTokenWrapper = this.handleInputVisualToken.bind(this);
|
||||
this.checkForEnterWrapper = this.checkForEnter.bind(this);
|
||||
this.onClearSearchWrapper = this.onClearSearch.bind(this);
|
||||
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
|
||||
this.checkForBackspaceWrapper = this.checkForBackspace.call(this);
|
||||
this.removeSelectedTokenKeydownWrapper = this.removeSelectedTokenKeydown.bind(this);
|
||||
this.unselectEditTokensWrapper = this.unselectEditTokens.bind(this);
|
||||
this.editTokenWrapper = this.editToken.bind(this);
|
||||
|
|
@ -180,22 +180,34 @@ class FilteredSearchManager {
|
|||
this.unbindStateEvents();
|
||||
}
|
||||
|
||||
checkForBackspace(e) {
|
||||
// 8 = Backspace Key
|
||||
// 46 = Delete Key
|
||||
if (e.keyCode === 8 || e.keyCode === 46) {
|
||||
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
checkForBackspace() {
|
||||
let backspaceCount = 0;
|
||||
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
|
||||
const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
|
||||
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
// closure for keeping track of the number of backspace keystrokes
|
||||
return (e) => {
|
||||
// 8 = Backspace Key
|
||||
// 46 = Delete Key
|
||||
if (e.keyCode === 8 || e.keyCode === 46) {
|
||||
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
|
||||
const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
|
||||
if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
|
||||
backspaceCount += 1;
|
||||
|
||||
if (backspaceCount === 2) {
|
||||
backspaceCount = 0;
|
||||
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
}
|
||||
}
|
||||
|
||||
// Reposition dropdown so that it is aligned with cursor
|
||||
this.dropdownManager.updateCurrentDropdownOffset();
|
||||
} else {
|
||||
backspaceCount = 0;
|
||||
}
|
||||
|
||||
// Reposition dropdown so that it is aligned with cursor
|
||||
this.dropdownManager.updateCurrentDropdownOffset();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
checkForEnter(e) {
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ export default {
|
|||
class="group-row"
|
||||
>
|
||||
<div
|
||||
class="group-row-contents">
|
||||
class="group-row-contents"
|
||||
:class="{ 'project-row-contents': !isGroup }">
|
||||
<item-actions
|
||||
v-if="isGroup"
|
||||
:group="group"
|
||||
|
|
@ -97,7 +98,7 @@ export default {
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
class="avatar-container s40 hidden-xs"
|
||||
class="avatar-container prepend-top-8 prepend-left-5 s24 hidden-xs"
|
||||
:class="{ 'content-loading': group.isChildrenLoading }"
|
||||
>
|
||||
<a
|
||||
|
|
@ -106,11 +107,12 @@ export default {
|
|||
>
|
||||
<img
|
||||
v-if="hasAvatar"
|
||||
class="avatar s40"
|
||||
class="avatar s24"
|
||||
:src="group.avatarUrl"
|
||||
/>
|
||||
<identicon
|
||||
v-else
|
||||
size-class="s24"
|
||||
:entity-id=group.id
|
||||
:entity-name="group.name"
|
||||
/>
|
||||
|
|
@ -123,7 +125,7 @@ export default {
|
|||
:href="group.relativePath"
|
||||
:title="group.fullName"
|
||||
class="no-expand"
|
||||
data-placement="top"
|
||||
data-placement="bottom"
|
||||
>{{
|
||||
// ending bracket must be by closing tag to prevent
|
||||
// link hover text-decoration from over-extending
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<script>
|
||||
import { s__ } from '../../locale';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
import modal from '../../vue_shared/components/modal.vue';
|
||||
import { s__ } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import modal from '~/vue_shared/components/modal.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { COMMON_STR } from '../constants';
|
||||
import Icon from '../../vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
icon,
|
||||
modal,
|
||||
},
|
||||
directives: {
|
||||
|
|
@ -64,10 +64,9 @@ export default {
|
|||
:title="editBtnTitle"
|
||||
:aria-label="editBtnTitle"
|
||||
data-container="body"
|
||||
data-placement="bottom"
|
||||
class="edit-group btn no-expand">
|
||||
<icon
|
||||
name="settings">
|
||||
</icon>
|
||||
<icon name="settings"/>
|
||||
</a>
|
||||
<a
|
||||
v-tooltip
|
||||
|
|
@ -77,10 +76,9 @@ export default {
|
|||
:title="leaveBtnTitle"
|
||||
:aria-label="leaveBtnTitle"
|
||||
data-container="body"
|
||||
data-placement="bottom"
|
||||
class="leave-group btn no-expand">
|
||||
<i
|
||||
class="fa fa-sign-out"
|
||||
aria-hidden="true"/>
|
||||
<icon name="leave"/>
|
||||
</a>
|
||||
<modal
|
||||
v-show="modalStatus"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
isGroupOpen: {
|
||||
|
|
@ -7,9 +9,12 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
return this.isGroupOpen ? 'fa-caret-down' : 'fa-caret-right';
|
||||
return this.isGroupOpen ? 'angle-down' : 'angle-right';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -17,9 +22,9 @@ export default {
|
|||
|
||||
<template>
|
||||
<span class="folder-caret">
|
||||
<i
|
||||
:class="iconClass"
|
||||
class="fa"
|
||||
aria-hidden="true"/>
|
||||
<icon
|
||||
:size="12"
|
||||
:name="iconClass"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<script>
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { ITEM_TYPE, VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE } from '../constants';
|
||||
import itemStatsValue from './item_stats_value.vue';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
components: {
|
||||
icon,
|
||||
timeAgoTooltip,
|
||||
itemStatsValue,
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
|
|
@ -34,65 +38,47 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="stats">
|
||||
<span
|
||||
v-tooltip
|
||||
<item-stats-value
|
||||
v-if="isGroup"
|
||||
css-class="number-subgroups"
|
||||
icon-name="folder"
|
||||
:title="s__('Subgroups')"
|
||||
class="number-subgroups"
|
||||
data-placement="top"
|
||||
data-container="body">
|
||||
<i
|
||||
class="fa fa-folder"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{item.subgroupCount}}
|
||||
</span>
|
||||
<span
|
||||
v-tooltip
|
||||
:value=item.subgroupCount
|
||||
/>
|
||||
<item-stats-value
|
||||
v-if="isGroup"
|
||||
css-class="number-projects"
|
||||
icon-name="bookmark"
|
||||
:title="s__('Projects')"
|
||||
class="number-projects"
|
||||
data-placement="top"
|
||||
data-container="body">
|
||||
<i
|
||||
class="fa fa-bookmark"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{item.projectCount}}
|
||||
</span>
|
||||
<span
|
||||
v-tooltip
|
||||
:value=item.projectCount
|
||||
/>
|
||||
<item-stats-value
|
||||
v-if="isGroup"
|
||||
css-class="number-users"
|
||||
icon-name="users"
|
||||
:title="s__('Members')"
|
||||
class="number-users"
|
||||
data-placement="top"
|
||||
data-container="body">
|
||||
<i
|
||||
class="fa fa-users"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{item.memberCount}}
|
||||
</span>
|
||||
<span
|
||||
:value=item.memberCount
|
||||
/>
|
||||
<item-stats-value
|
||||
v-if="isProject"
|
||||
class="project-stars">
|
||||
<i
|
||||
class="fa fa-star"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{item.starCount}}
|
||||
</span>
|
||||
<span
|
||||
v-tooltip
|
||||
css-class="project-stars"
|
||||
icon-name="star"
|
||||
:value=item.starCount
|
||||
/>
|
||||
<item-stats-value
|
||||
css-class="item-visibility"
|
||||
tooltip-placement="left"
|
||||
:icon-name="visibilityIcon"
|
||||
:title="visibilityTooltip"
|
||||
data-placement="left"
|
||||
data-container="body"
|
||||
class="item-visibility">
|
||||
<i
|
||||
:class="visibilityIcon"
|
||||
class="fa"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
class="last-updated"
|
||||
v-if="isProject"
|
||||
>
|
||||
<time-ago-tooltip
|
||||
tooltip-placement="bottom"
|
||||
:time="item.updatedAt"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
<script>
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
iconName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
tooltipPlacement: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'bottom',
|
||||
},
|
||||
/**
|
||||
* value could either be number or string
|
||||
* as `memberCount` is always passed as string
|
||||
* while `subgroupCount` & `projectCount`
|
||||
* are always number
|
||||
*/
|
||||
value: {
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
isValuePresent() {
|
||||
return this.value !== '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-tooltip
|
||||
data-container="body"
|
||||
:data-placement="tooltipPlacement"
|
||||
:class="cssClass"
|
||||
:title="title"
|
||||
>
|
||||
<icon :name="iconName"/>
|
||||
<span
|
||||
v-if="isValuePresent"
|
||||
class="stat-value"
|
||||
>
|
||||
{{value}}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
<script>
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import { ITEM_TYPE } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
itemType: {
|
||||
type: String,
|
||||
|
|
@ -16,9 +20,9 @@ export default {
|
|||
computed: {
|
||||
iconClass() {
|
||||
if (this.itemType === ITEM_TYPE.GROUP) {
|
||||
return this.isGroupOpen ? 'fa-folder-open' : 'fa-folder';
|
||||
return this.isGroupOpen ? 'folder-open' : 'folder';
|
||||
}
|
||||
return 'fa-bookmark';
|
||||
return 'bookmark';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -26,9 +30,6 @@ export default {
|
|||
|
||||
<template>
|
||||
<span class="item-type-icon">
|
||||
<i
|
||||
:class="iconClass"
|
||||
class="fa"
|
||||
aria-hidden="true"/>
|
||||
<icon :name="iconClass"/>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const PROJECT_VISIBILITY_TYPE = {
|
|||
};
|
||||
|
||||
export const VISIBILITY_TYPE_ICON = {
|
||||
public: 'fa-globe',
|
||||
internal: 'fa-shield',
|
||||
private: 'fa-lock',
|
||||
public: 'earth',
|
||||
internal: 'shield',
|
||||
private: 'lock',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export default class GroupsStore {
|
|||
subgroupCount: rawGroupItem.subgroup_count,
|
||||
memberCount: rawGroupItem.number_users_with_delimiter,
|
||||
starCount: rawGroupItem.star_count,
|
||||
updatedAt: rawGroupItem.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,18 @@
|
|||
import { mapGetters, mapState, mapActions } from 'vuex';
|
||||
import repoCommitSection from './repo_commit_section.vue';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
width: 290,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
repoCommitSection,
|
||||
icon,
|
||||
panelResizer,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
|
@ -18,10 +25,20 @@ export default {
|
|||
currentIcon() {
|
||||
return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
|
||||
},
|
||||
maxSize() {
|
||||
return window.innerWidth / 2;
|
||||
},
|
||||
panelStyle() {
|
||||
if (!this.rightPanelCollapsed) {
|
||||
return { width: `${this.width}px` };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setPanelCollapsedStatus',
|
||||
'setResizingStatus',
|
||||
]),
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
|
|
@ -29,6 +46,12 @@ export default {
|
|||
collapsed: !this.rightPanelCollapsed,
|
||||
});
|
||||
},
|
||||
resizingStarted() {
|
||||
this.setResizingStatus(true);
|
||||
},
|
||||
resizingEnded() {
|
||||
this.setResizingStatus(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -39,6 +62,7 @@ export default {
|
|||
:class="{
|
||||
'is-collapsed': rightPanelCollapsed,
|
||||
}"
|
||||
:style="panelStyle"
|
||||
>
|
||||
<div
|
||||
class="multi-file-commit-panel-section">
|
||||
|
|
@ -71,5 +95,14 @@ export default {
|
|||
<repo-commit-section
|
||||
class=""/>
|
||||
</div>
|
||||
<panel-resizer
|
||||
:size.sync="width"
|
||||
:enabled="!rightPanelCollapsed"
|
||||
:start-size="290"
|
||||
:min-size="200"
|
||||
:max-size="maxSize"
|
||||
@resize-start="resizingStarted"
|
||||
@resize-end="resizingEnded"
|
||||
side="left"/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,18 @@
|
|||
import { mapState, mapActions } from 'vuex';
|
||||
import projectTree from './ide_project_tree.vue';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
import panelResizer from '../../vue_shared/components/panel_resizer.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
width: 290,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
projectTree,
|
||||
icon,
|
||||
panelResizer,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
|
@ -16,10 +23,20 @@ export default {
|
|||
currentIcon() {
|
||||
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
|
||||
},
|
||||
maxSize() {
|
||||
return window.innerWidth / 2;
|
||||
},
|
||||
panelStyle() {
|
||||
if (!this.leftPanelCollapsed) {
|
||||
return { width: `${this.width}px` };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setPanelCollapsedStatus',
|
||||
'setResizingStatus',
|
||||
]),
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
|
|
@ -27,6 +44,12 @@ export default {
|
|||
collapsed: !this.leftPanelCollapsed,
|
||||
});
|
||||
},
|
||||
resizingStarted() {
|
||||
this.setResizingStatus(true);
|
||||
},
|
||||
resizingEnded() {
|
||||
this.setResizingStatus(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -37,6 +60,7 @@ export default {
|
|||
:class="{
|
||||
'is-collapsed': leftPanelCollapsed,
|
||||
}"
|
||||
:style="panelStyle"
|
||||
>
|
||||
<div class="multi-file-commit-panel-inner">
|
||||
<project-tree
|
||||
|
|
@ -58,5 +82,14 @@ export default {
|
|||
class="collapse-text"
|
||||
>Collapse sidebar</span>
|
||||
</button>
|
||||
<panel-resizer
|
||||
:size.sync="width"
|
||||
:enabled="!leftPanelCollapsed"
|
||||
:start-size="290"
|
||||
:min-size="200"
|
||||
:max-size="maxSize"
|
||||
@resize-start="resizingStarted"
|
||||
@resize-end="resizingEnded"
|
||||
side="right"/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -90,6 +90,11 @@ export default {
|
|||
rightPanelCollapsed() {
|
||||
this.editor.updateDimensions();
|
||||
},
|
||||
panelResizing(isResizing) {
|
||||
if (isResizing === false) {
|
||||
this.editor.updateDimensions();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
|
|
@ -99,6 +104,7 @@ export default {
|
|||
...mapState([
|
||||
'leftPanelCollapsed',
|
||||
'rightPanelCollapsed',
|
||||
'panelResizing',
|
||||
]),
|
||||
shouldHideEditor() {
|
||||
return this.activeFile.binary && !this.activeFile.raw;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import timeAgoMixin from '../../vue_shared/mixins/timeago';
|
||||
import skeletonLoadingContainer from '../../vue_shared/components/skeleton_loading_container.vue';
|
||||
import newDropdown from './new_dropdown/index.vue';
|
||||
import fileIcon from '../../vue_shared/components/file_icon.vue';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
|
|
@ -11,6 +12,7 @@
|
|||
components: {
|
||||
skeletonLoadingContainer,
|
||||
newDropdown,
|
||||
fileIcon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
|
|
@ -26,13 +28,6 @@
|
|||
...mapState([
|
||||
'leftPanelCollapsed',
|
||||
]),
|
||||
fileIcon() {
|
||||
return {
|
||||
'fa-spinner fa-spin': this.file.loading,
|
||||
[this.file.icon]: !this.file.loading,
|
||||
'fa-folder-open': !this.file.loading && this.file.opened,
|
||||
};
|
||||
},
|
||||
isSubmodule() {
|
||||
return this.file.type === 'submodule';
|
||||
},
|
||||
|
|
@ -94,16 +89,18 @@
|
|||
class="multi-file-table-name"
|
||||
:colspan="submoduleColSpan"
|
||||
>
|
||||
<i
|
||||
class="fa fa-fw file-icon"
|
||||
:class="fileIcon"
|
||||
:style="levelIndentation"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<a
|
||||
class="repo-file-name"
|
||||
>
|
||||
<file-icon
|
||||
:file-name="file.name"
|
||||
:loading="file.loading"
|
||||
:folder="file.type === 'tree'"
|
||||
:opened="file.opened"
|
||||
:style="levelIndentation"
|
||||
:size="16"
|
||||
>
|
||||
</file-icon>
|
||||
{{ file.name }}
|
||||
</a>
|
||||
<new-dropdown
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import fileIcon from '../../vue_shared/components/file_icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -8,7 +9,9 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
fileIcon,
|
||||
},
|
||||
computed: {
|
||||
closeLabel() {
|
||||
if (this.tab.changed || this.tab.tempFile) {
|
||||
|
|
@ -63,6 +66,11 @@ export default {
|
|||
:class="{active : tab.active }"
|
||||
:title="tab.url"
|
||||
>
|
||||
<file-icon
|
||||
:file-name="tab.name"
|
||||
:size="16"
|
||||
>
|
||||
</file-icon>
|
||||
{{ tab.name }}
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const setResizingStatus = ({ commit }, resizing) => {
|
||||
commit(types.SET_RESIZING_STATUS, resizing);
|
||||
};
|
||||
|
||||
export const checkCommitStatus = ({ state }) =>
|
||||
service
|
||||
.getBranchData(state.currentProjectId, state.currentBranchId)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export const SET_ROOT = 'SET_ROOT';
|
|||
export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
|
||||
export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
|
||||
export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
|
||||
export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
|
||||
|
||||
// Project Mutation Types
|
||||
export const SET_PROJECT = 'SET_PROJECT';
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ export default {
|
|||
rightPanelCollapsed: collapsed,
|
||||
});
|
||||
},
|
||||
[types.SET_RESIZING_STATUS](state, resizing) {
|
||||
Object.assign(state, {
|
||||
panelResizing: resizing,
|
||||
});
|
||||
},
|
||||
[types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
|
||||
Object.assign(entry.lastCommit, {
|
||||
id: lastCommit.commit.id,
|
||||
|
|
|
|||
|
|
@ -19,4 +19,5 @@ export default () => ({
|
|||
projects: {},
|
||||
leftPanelCollapsed: false,
|
||||
rightPanelCollapsed: true,
|
||||
panelResizing: false,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable no-new */
|
||||
/* global MilestoneSelect */
|
||||
|
||||
import MilestoneSelect from './milestone_select';
|
||||
import LabelsSelect from './labels_select';
|
||||
import IssuableContext from './issuable_context';
|
||||
import Sidebar from './right_sidebar';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
/* eslint-disable no-new */
|
||||
import LabelsSelect from './labels_select';
|
||||
/* global MilestoneSelect */
|
||||
import subscriptionSelect from './subscription_select';
|
||||
import UsersSelect from './users_select';
|
||||
import issueStatusSelect from './issue_status_select';
|
||||
import MilestoneSelect from './milestone_select';
|
||||
|
||||
export default () => {
|
||||
new UsersSelect();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
/* eslint-disable class-methods-use-this, no-new */
|
||||
/* global MilestoneSelect */
|
||||
|
||||
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
|
||||
import './milestone_select';
|
||||
import MilestoneSelect from './milestone_select';
|
||||
import issueStatusSelect from './issue_status_select';
|
||||
import subscriptionSelect from './subscription_select';
|
||||
import LabelsSelect from './labels_select';
|
||||
|
|
|
|||
|
|
@ -172,8 +172,8 @@ export default {
|
|||
},
|
||||
|
||||
updateIssuable() {
|
||||
this.service.updateIssuable(this.store.formState)
|
||||
.then(res => res.json())
|
||||
return this.service.updateIssuable(this.store.formState)
|
||||
.then(res => res.data)
|
||||
.then(data => this.checkForSpam(data))
|
||||
.then((data) => {
|
||||
if (location.pathname !== data.web_url) {
|
||||
|
|
@ -182,7 +182,7 @@ export default {
|
|||
|
||||
return this.service.getData();
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.store.updateState(data);
|
||||
eventHub.$emit('close.form');
|
||||
|
|
@ -207,7 +207,7 @@ export default {
|
|||
|
||||
deleteIssuable() {
|
||||
this.service.deleteIssuable()
|
||||
.then(res => res.json())
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
// Stop the poll so we don't get 404's with the issuable not existing
|
||||
this.poll.stop();
|
||||
|
|
@ -225,7 +225,7 @@ export default {
|
|||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'getData',
|
||||
successCallback: res => res.json().then(data => this.store.updateState(data)),
|
||||
successCallback: res => this.store.updateState(res.data),
|
||||
errorCallback(err) {
|
||||
throw new Error(err);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,29 +1,20 @@
|
|||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
|
||||
Vue.use(VueResource);
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
|
||||
export default class Service {
|
||||
constructor(endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
|
||||
this.resource = Vue.resource(`${this.endpoint}.json`, {}, {
|
||||
realtimeChanges: {
|
||||
method: 'GET',
|
||||
url: `${this.endpoint}/realtime_changes`,
|
||||
},
|
||||
});
|
||||
this.endpoint = `${endpoint}.json`;
|
||||
this.realtimeEndpoint = `${endpoint}/realtime_changes`;
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.resource.realtimeChanges();
|
||||
return axios.get(this.realtimeEndpoint);
|
||||
}
|
||||
|
||||
deleteIssuable() {
|
||||
return this.resource.delete();
|
||||
return axios.delete(this.endpoint);
|
||||
}
|
||||
|
||||
updateIssuable(data) {
|
||||
return this.resource.update(data);
|
||||
return axios.put(this.endpoint, data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'underscore';
|
||||
import { visitUrl } from './lib/utils/url_utility';
|
||||
import bp from './breakpoints';
|
||||
import { bytesToKiB } from './lib/utils/number_utils';
|
||||
import { numberToHumanSize } from './lib/utils/number_utils';
|
||||
import { setCiStatusFavicon } from './lib/utils/common_utils';
|
||||
import { timeFor } from './lib/utils/datetime_utility';
|
||||
|
||||
|
|
@ -96,14 +96,15 @@ export default class Job {
|
|||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
canScroll() {
|
||||
return this.$document.height() > this.$window.height();
|
||||
return $(document).height() > $(window).height();
|
||||
}
|
||||
|
||||
toggleScroll() {
|
||||
const currentPosition = this.$document.scrollTop();
|
||||
const scrollHeight = this.$document.height();
|
||||
const $document = $(document);
|
||||
const currentPosition = $document.scrollTop();
|
||||
const scrollHeight = $document.height();
|
||||
|
||||
const windowHeight = this.$window.height();
|
||||
const windowHeight = $(window).height();
|
||||
if (this.canScroll()) {
|
||||
if (currentPosition > 0 &&
|
||||
(scrollHeight - currentPosition !== windowHeight)) {
|
||||
|
|
@ -127,18 +128,22 @@ export default class Job {
|
|||
this.toggleDisableButton(this.$scrollBottomBtn, true);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
isScrolledToBottom() {
|
||||
const currentPosition = this.$document.scrollTop();
|
||||
const scrollHeight = this.$document.height();
|
||||
const $document = $(document);
|
||||
|
||||
const currentPosition = $document.scrollTop();
|
||||
const scrollHeight = $document.height();
|
||||
|
||||
const windowHeight = $(window).height();
|
||||
|
||||
const windowHeight = this.$window.height();
|
||||
return scrollHeight - currentPosition === windowHeight;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
scrollDown() {
|
||||
this.$document.scrollTop(this.$document.height());
|
||||
const $document = $(document);
|
||||
$document.scrollTop($document.height());
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
|
|
@ -148,7 +153,7 @@ export default class Job {
|
|||
}
|
||||
|
||||
scrollToTop() {
|
||||
this.$document.scrollTop(0);
|
||||
$(document).scrollTop(0);
|
||||
this.hasBeenScrolled = true;
|
||||
this.toggleScroll();
|
||||
}
|
||||
|
|
@ -193,7 +198,7 @@ export default class Job {
|
|||
// we need to show a message warning the user about that.
|
||||
if (this.logBytes < log.total) {
|
||||
// size is in bytes, we need to calculate KiB
|
||||
const size = bytesToKiB(this.logBytes);
|
||||
const size = numberToHumanSize(this.logBytes);
|
||||
$('.js-truncated-info-size').html(`${size}`);
|
||||
this.$truncatedInfo.removeClass('hidden');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import axios from 'axios';
|
|||
import csrf from './csrf';
|
||||
|
||||
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
|
||||
// Used by Rails to check if it is a valid XHR request
|
||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
// Maintain a global counter for active requests
|
||||
// see: spec/support/wait_for_requests.rb
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ export const nodeMatchesSelector = (node, selector) => {
|
|||
export const normalizeHeaders = (headers) => {
|
||||
const upperCaseHeaders = {};
|
||||
|
||||
Object.keys(headers).forEach((e) => {
|
||||
Object.keys(headers || {}).forEach((e) => {
|
||||
upperCaseHeaders[e.toUpperCase()] = headers[e];
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function getParameterValues(sParam) {
|
|||
// @param {String} url
|
||||
export function mergeUrlParams(params, url) {
|
||||
let newUrl = Object.keys(params).reduce((acc, paramName) => {
|
||||
const paramValue = params[paramName];
|
||||
const paramValue = encodeURIComponent(params[paramName]);
|
||||
const pattern = new RegExp(`\\b(${paramName}=).*?(&|$)`);
|
||||
|
||||
if (paramValue === null) {
|
||||
|
|
|
|||
|
|
@ -1,237 +1,228 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
|
||||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
|
||||
/* global Issuable */
|
||||
/* global ListMilestone */
|
||||
import _ from 'underscore';
|
||||
import { timeFor } from './lib/utils/datetime_utility';
|
||||
|
||||
(function() {
|
||||
this.MilestoneSelect = (function() {
|
||||
function MilestoneSelect(currentProject, els, options = {}) {
|
||||
var _this, $els;
|
||||
if (currentProject != null) {
|
||||
_this = this;
|
||||
this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
|
||||
}
|
||||
|
||||
$els = $(els);
|
||||
|
||||
if (!els) {
|
||||
$els = $('.js-milestone-select');
|
||||
}
|
||||
|
||||
$els.each(function(i, dropdown) {
|
||||
var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, defaultNo, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, selectedMilestoneDefault, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove;
|
||||
$dropdown = $(dropdown);
|
||||
projectId = $dropdown.data('project-id');
|
||||
milestonesUrl = $dropdown.data('milestones');
|
||||
issueUpdateURL = $dropdown.data('issueUpdate');
|
||||
showNo = $dropdown.data('show-no');
|
||||
showAny = $dropdown.data('show-any');
|
||||
showMenuAbove = $dropdown.data('showMenuAbove');
|
||||
showUpcoming = $dropdown.data('show-upcoming');
|
||||
showStarted = $dropdown.data('show-started');
|
||||
useId = $dropdown.data('use-id');
|
||||
defaultLabel = $dropdown.data('default-label');
|
||||
defaultNo = $dropdown.data('default-no');
|
||||
issuableId = $dropdown.data('issuable-id');
|
||||
abilityName = $dropdown.data('ability-name');
|
||||
$selectbox = $dropdown.closest('.selectbox');
|
||||
$block = $selectbox.closest('.block');
|
||||
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
|
||||
$value = $block.find('.value');
|
||||
$loading = $block.find('.block-loading').fadeOut();
|
||||
selectedMilestoneDefault = (showAny ? '' : null);
|
||||
selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault);
|
||||
selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
|
||||
if (issueUpdateURL) {
|
||||
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
|
||||
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
|
||||
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
|
||||
}
|
||||
return $dropdown.glDropdown({
|
||||
showMenuAbove: showMenuAbove,
|
||||
data: function(term, callback) {
|
||||
return $.ajax({
|
||||
url: milestonesUrl
|
||||
}).done(function(data) {
|
||||
var extraOptions = [];
|
||||
if (showAny) {
|
||||
extraOptions.push({
|
||||
id: 0,
|
||||
name: '',
|
||||
title: 'Any Milestone'
|
||||
});
|
||||
}
|
||||
if (showNo) {
|
||||
extraOptions.push({
|
||||
id: -1,
|
||||
name: 'No Milestone',
|
||||
title: 'No Milestone'
|
||||
});
|
||||
}
|
||||
if (showUpcoming) {
|
||||
extraOptions.push({
|
||||
id: -2,
|
||||
name: '#upcoming',
|
||||
title: 'Upcoming'
|
||||
});
|
||||
}
|
||||
if (showStarted) {
|
||||
extraOptions.push({
|
||||
id: -3,
|
||||
name: '#started',
|
||||
title: 'Started'
|
||||
});
|
||||
}
|
||||
if (extraOptions.length) {
|
||||
extraOptions.push('divider');
|
||||
}
|
||||
|
||||
callback(extraOptions.concat(data));
|
||||
if (showMenuAbove) {
|
||||
$dropdown.data('glDropdown').positionMenuAbove();
|
||||
}
|
||||
$(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
|
||||
});
|
||||
},
|
||||
renderRow: function(milestone) {
|
||||
return `
|
||||
<li data-milestone-id="${milestone.name}">
|
||||
<a href='#' class='dropdown-menu-milestone-link'>
|
||||
${_.escape(milestone.title)}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
filterable: true,
|
||||
search: {
|
||||
fields: ['title']
|
||||
},
|
||||
selectable: true,
|
||||
toggleLabel: function(selected, el, e) {
|
||||
if (selected && 'id' in selected && $(el).hasClass('is-active')) {
|
||||
return selected.title;
|
||||
} else {
|
||||
return defaultLabel;
|
||||
}
|
||||
},
|
||||
defaultLabel: defaultLabel,
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
text: function(milestone) {
|
||||
return _.escape(milestone.title);
|
||||
},
|
||||
id: function(milestone) {
|
||||
if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
|
||||
return milestone.name;
|
||||
} else {
|
||||
return milestone.id;
|
||||
}
|
||||
},
|
||||
isSelected: function(milestone) {
|
||||
return milestone.name === selectedMilestone;
|
||||
},
|
||||
hidden: function() {
|
||||
$selectbox.hide();
|
||||
// display:block overrides the hide-collapse rule
|
||||
return $value.css('display', '');
|
||||
},
|
||||
opened: function(e) {
|
||||
const $el = $(e.currentTarget);
|
||||
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
|
||||
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
|
||||
}
|
||||
$('a.is-active', $el).removeClass('is-active');
|
||||
$(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
|
||||
},
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: function(clickEvent) {
|
||||
const { $el, e } = clickEvent;
|
||||
let selected = clickEvent.selectedObj;
|
||||
|
||||
var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore;
|
||||
if (!selected) return;
|
||||
|
||||
if (options.handleClick) {
|
||||
e.preventDefault();
|
||||
options.handleClick(selected);
|
||||
return;
|
||||
}
|
||||
|
||||
page = $('body').attr('data-page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = (page === page && page === 'projects:merge_requests:index');
|
||||
isSelecting = (selected.name !== selectedMilestone);
|
||||
selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
|
||||
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dropdown.closest('.add-issues-modal').length) {
|
||||
boardsStore = gl.issueBoards.ModalStore.store.filter;
|
||||
}
|
||||
|
||||
if (boardsStore) {
|
||||
boardsStore[$dropdown.data('field-name')] = selected.name;
|
||||
e.preventDefault();
|
||||
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
return Issuable.filterResults($dropdown.closest('form'));
|
||||
} else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
return $dropdown.closest('form').submit();
|
||||
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
|
||||
if (selected.id !== -1 && isSelecting) {
|
||||
gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({
|
||||
id: selected.id,
|
||||
title: selected.name
|
||||
}));
|
||||
} else {
|
||||
gl.issueBoards.boardStoreIssueDelete('milestone');
|
||||
}
|
||||
|
||||
$dropdown.trigger('loading.gl.dropdown');
|
||||
$loading.removeClass('hidden').fadeIn();
|
||||
|
||||
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
|
||||
.then(function () {
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$loading.fadeOut();
|
||||
})
|
||||
.catch(() => {
|
||||
$loading.fadeOut();
|
||||
});
|
||||
} else {
|
||||
selected = $selectbox.find('input[type="hidden"]').val();
|
||||
data = {};
|
||||
data[abilityName] = {};
|
||||
data[abilityName].milestone_id = selected != null ? selected : null;
|
||||
$loading.removeClass('hidden').fadeIn();
|
||||
$dropdown.trigger('loading.gl.dropdown');
|
||||
return $.ajax({
|
||||
type: 'PUT',
|
||||
url: issueUpdateURL,
|
||||
data: data
|
||||
}).done(function(data) {
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$loading.fadeOut();
|
||||
$selectbox.hide();
|
||||
$value.css('display', '');
|
||||
if (data.milestone != null) {
|
||||
data.milestone.full_path = _this.currentProject.full_path;
|
||||
data.milestone.remaining = timeFor(data.milestone.due_date);
|
||||
data.milestone.name = data.milestone.title;
|
||||
$value.html(milestoneLinkTemplate(data.milestone));
|
||||
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
|
||||
} else {
|
||||
$value.html(milestoneLinkNoneTemplate);
|
||||
return $sidebarCollapsedValue.find('span').text('No');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
export default class MilestoneSelect {
|
||||
constructor(currentProject, els, options = {}) {
|
||||
if (currentProject !== null) {
|
||||
this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
|
||||
}
|
||||
|
||||
return MilestoneSelect;
|
||||
})();
|
||||
}).call(window);
|
||||
this.init(els, options);
|
||||
}
|
||||
|
||||
init(els, options) {
|
||||
let $els = $(els);
|
||||
|
||||
if (!els) {
|
||||
$els = $('.js-milestone-select');
|
||||
}
|
||||
|
||||
$els.each((i, dropdown) => {
|
||||
let collapsedSidebarLabelTemplate, milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault;
|
||||
const $dropdown = $(dropdown);
|
||||
const projectId = $dropdown.data('project-id');
|
||||
const milestonesUrl = $dropdown.data('milestones');
|
||||
const issueUpdateURL = $dropdown.data('issueUpdate');
|
||||
const showNo = $dropdown.data('show-no');
|
||||
const showAny = $dropdown.data('show-any');
|
||||
const showMenuAbove = $dropdown.data('showMenuAbove');
|
||||
const showUpcoming = $dropdown.data('show-upcoming');
|
||||
const showStarted = $dropdown.data('show-started');
|
||||
const useId = $dropdown.data('use-id');
|
||||
const defaultLabel = $dropdown.data('default-label');
|
||||
const defaultNo = $dropdown.data('default-no');
|
||||
const issuableId = $dropdown.data('issuable-id');
|
||||
const abilityName = $dropdown.data('ability-name');
|
||||
const $selectBox = $dropdown.closest('.selectbox');
|
||||
const $block = $selectBox.closest('.block');
|
||||
const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
|
||||
const $value = $block.find('.value');
|
||||
const $loading = $block.find('.block-loading').fadeOut();
|
||||
selectedMilestoneDefault = (showAny ? '' : null);
|
||||
selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault);
|
||||
selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
|
||||
|
||||
if (issueUpdateURL) {
|
||||
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
|
||||
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
|
||||
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>');
|
||||
}
|
||||
return $dropdown.glDropdown({
|
||||
showMenuAbove: showMenuAbove,
|
||||
data: (term, callback) => $.ajax({
|
||||
url: milestonesUrl
|
||||
}).done((data) => {
|
||||
const extraOptions = [];
|
||||
if (showAny) {
|
||||
extraOptions.push({
|
||||
id: 0,
|
||||
name: '',
|
||||
title: 'Any Milestone'
|
||||
});
|
||||
}
|
||||
if (showNo) {
|
||||
extraOptions.push({
|
||||
id: -1,
|
||||
name: 'No Milestone',
|
||||
title: 'No Milestone'
|
||||
});
|
||||
}
|
||||
if (showUpcoming) {
|
||||
extraOptions.push({
|
||||
id: -2,
|
||||
name: '#upcoming',
|
||||
title: 'Upcoming'
|
||||
});
|
||||
}
|
||||
if (showStarted) {
|
||||
extraOptions.push({
|
||||
id: -3,
|
||||
name: '#started',
|
||||
title: 'Started'
|
||||
});
|
||||
}
|
||||
if (extraOptions.length) {
|
||||
extraOptions.push('divider');
|
||||
}
|
||||
|
||||
callback(extraOptions.concat(data));
|
||||
if (showMenuAbove) {
|
||||
$dropdown.data('glDropdown').positionMenuAbove();
|
||||
}
|
||||
$(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
|
||||
}),
|
||||
renderRow: milestone => `
|
||||
<li data-milestone-id="${milestone.name}">
|
||||
<a href='#' class='dropdown-menu-milestone-link'>
|
||||
${_.escape(milestone.title)}
|
||||
</a>
|
||||
</li>
|
||||
`,
|
||||
filterable: true,
|
||||
search: {
|
||||
fields: ['title']
|
||||
},
|
||||
selectable: true,
|
||||
toggleLabel: (selected, el, e) => {
|
||||
if (selected && 'id' in selected && $(el).hasClass('is-active')) {
|
||||
return selected.title;
|
||||
} else {
|
||||
return defaultLabel;
|
||||
}
|
||||
},
|
||||
defaultLabel: defaultLabel,
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
text: milestone => _.escape(milestone.title),
|
||||
id: (milestone) => {
|
||||
if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
|
||||
return milestone.name;
|
||||
} else {
|
||||
return milestone.id;
|
||||
}
|
||||
},
|
||||
isSelected: milestone => milestone.name === selectedMilestone,
|
||||
hidden: () => {
|
||||
$selectBox.hide();
|
||||
// display:block overrides the hide-collapse rule
|
||||
return $value.css('display', '');
|
||||
},
|
||||
opened: (e) => {
|
||||
const $el = $(e.currentTarget);
|
||||
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
|
||||
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
|
||||
}
|
||||
$('a.is-active', $el).removeClass('is-active');
|
||||
$(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
|
||||
},
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: (clickEvent) => {
|
||||
const { $el, e } = clickEvent;
|
||||
let selected = clickEvent.selectedObj;
|
||||
|
||||
let data, boardsStore;
|
||||
if (!selected) return;
|
||||
|
||||
if (options.handleClick) {
|
||||
e.preventDefault();
|
||||
options.handleClick(selected);
|
||||
return;
|
||||
}
|
||||
|
||||
const page = $('body').attr('data-page');
|
||||
const isIssueIndex = page === 'projects:issues:index';
|
||||
const isMRIndex = (page === page && page === 'projects:merge_requests:index');
|
||||
const isSelecting = (selected.name !== selectedMilestone);
|
||||
selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
|
||||
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dropdown.closest('.add-issues-modal').length) {
|
||||
boardsStore = gl.issueBoards.ModalStore.store.filter;
|
||||
}
|
||||
|
||||
if (boardsStore) {
|
||||
boardsStore[$dropdown.data('field-name')] = selected.name;
|
||||
e.preventDefault();
|
||||
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
return Issuable.filterResults($dropdown.closest('form'));
|
||||
} else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
return $dropdown.closest('form').submit();
|
||||
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
|
||||
if (selected.id !== -1 && isSelecting) {
|
||||
gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({
|
||||
id: selected.id,
|
||||
title: selected.name
|
||||
}));
|
||||
} else {
|
||||
gl.issueBoards.boardStoreIssueDelete('milestone');
|
||||
}
|
||||
|
||||
$dropdown.trigger('loading.gl.dropdown');
|
||||
$loading.removeClass('hidden').fadeIn();
|
||||
|
||||
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
|
||||
.then(() => {
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$loading.fadeOut();
|
||||
})
|
||||
.catch(() => {
|
||||
$loading.fadeOut();
|
||||
});
|
||||
} else {
|
||||
selected = $selectBox.find('input[type="hidden"]').val();
|
||||
data = {};
|
||||
data[abilityName] = {};
|
||||
data[abilityName].milestone_id = selected != null ? selected : null;
|
||||
$loading.removeClass('hidden').fadeIn();
|
||||
$dropdown.trigger('loading.gl.dropdown');
|
||||
return $.ajax({
|
||||
type: 'PUT',
|
||||
url: issueUpdateURL,
|
||||
data: data
|
||||
}).done((data) => {
|
||||
$dropdown.trigger('loaded.gl.dropdown');
|
||||
$loading.fadeOut();
|
||||
$selectBox.hide();
|
||||
$value.css('display', '');
|
||||
if (data.milestone != null) {
|
||||
data.milestone.full_path = this.currentProject.full_path;
|
||||
data.milestone.remaining = timeFor(data.milestone.due_date);
|
||||
data.milestone.name = data.milestone.title;
|
||||
$value.html(milestoneLinkTemplate(data.milestone));
|
||||
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
|
||||
} else {
|
||||
$value.html(milestoneLinkNoneTemplate);
|
||||
return $sidebarCollapsedValue.find('span').text('No');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
import { timeFormat as time } from 'd3-time-format';
|
||||
import { timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3-time';
|
||||
import { timeSecond, timeMinute, timeHour, timeDay, timeWeek, timeMonth, timeYear } from 'd3-time';
|
||||
import { bisector } from 'd3-array';
|
||||
|
||||
const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear };
|
||||
const d3 = {
|
||||
time,
|
||||
bisector,
|
||||
timeSecond,
|
||||
timeMinute,
|
||||
timeHour,
|
||||
timeDay,
|
||||
timeWeek,
|
||||
timeMonth,
|
||||
timeYear,
|
||||
};
|
||||
|
||||
export const dateFormat = d3.time('%b %-d, %Y');
|
||||
export const timeFormat = d3.time('%-I:%M%p');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
|
||||
import Cookies from 'js-cookie';
|
||||
import Flash from '../flash';
|
||||
import { getPagePath } from '../lib/utils/common_utils';
|
||||
|
||||
|
|
@ -7,6 +8,8 @@ import { getPagePath } from '../lib/utils/common_utils';
|
|||
constructor({ form } = {}) {
|
||||
this.onSubmitForm = this.onSubmitForm.bind(this);
|
||||
this.form = form || $('.edit-user');
|
||||
this.newRepoActivated = Cookies.get('new_repo');
|
||||
this.setRepoRadio();
|
||||
this.bindEvents();
|
||||
this.initAvatarGlCrop();
|
||||
}
|
||||
|
|
@ -25,6 +28,7 @@ import { getPagePath } from '../lib/utils/common_utils';
|
|||
|
||||
bindEvents() {
|
||||
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
|
||||
$('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie);
|
||||
$('#user_notification_email').on('change', this.submitForm);
|
||||
$('#user_notified_of_own_activity').on('change', this.submitForm);
|
||||
$('.update-username').on('ajax:before', this.beforeUpdateUsername);
|
||||
|
|
@ -82,6 +86,23 @@ import { getPagePath } from '../lib/utils/common_utils';
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
setNewRepoCookie() {
|
||||
if (this.value === 'off') {
|
||||
Cookies.remove('new_repo');
|
||||
} else {
|
||||
Cookies.set('new_repo', true, { expires_in: 365 });
|
||||
}
|
||||
}
|
||||
|
||||
setRepoRadio() {
|
||||
const multiEditRadios = $('input[name="user[multi_file]"]');
|
||||
if (this.newRepoActivated || this.newRepoActivated === 'true') {
|
||||
multiEditRadios.filter('[value=on]').prop('checked', true);
|
||||
} else {
|
||||
multiEditRadios.filter('[value=off]').prop('checked', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ export default {
|
|||
|
||||
if (isConfirmed) {
|
||||
MRWidgetService.stopEnvironment(deployment.stop_url)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (res.redirect_url) {
|
||||
visitUrl(res.redirect_url);
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
if (data.redirect_url) {
|
||||
visitUrl(data.redirect_url);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
|
|||
|
|
@ -102,11 +102,11 @@ export default {
|
|||
return res;
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return res.data;
|
||||
})
|
||||
.then((res) => {
|
||||
this.computeGraphData(res.metrics, res.deployment_time);
|
||||
return res;
|
||||
.then((data) => {
|
||||
this.computeGraphData(data.metrics, data.deployment_time);
|
||||
return data;
|
||||
})
|
||||
.catch(() => {
|
||||
this.loadFailed = true;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ export default {
|
|||
<div class="media-body">
|
||||
<mr-widget-author-and-time
|
||||
actionText="Closed by"
|
||||
:author="mr.closedEvent.author"
|
||||
:dateTitle="mr.closedEvent.updatedAt"
|
||||
:dateReadable="mr.closedEvent.formattedUpdatedAt"
|
||||
:author="mr.metrics.closedBy"
|
||||
:dateTitle="mr.metrics.closedAt"
|
||||
:dateReadable="mr.metrics.readableClosedAt"
|
||||
/>
|
||||
<section class="mr-info-list">
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ export default {
|
|||
cancelAutomaticMerge() {
|
||||
this.isCancellingAutoMerge = true;
|
||||
this.service.cancelAutomaticMerge()
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
eventHub.$emit('UpdateWidgetData', res);
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
eventHub.$emit('UpdateWidgetData', data);
|
||||
})
|
||||
.catch(() => {
|
||||
this.isCancellingAutoMerge = false;
|
||||
|
|
@ -49,9 +49,9 @@ export default {
|
|||
|
||||
this.isRemovingSourceBranch = true;
|
||||
this.service.mergeResource.save(options)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (res.status === 'merge_when_pipeline_succeeds') {
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
if (data.status === 'merge_when_pipeline_succeeds') {
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ export default {
|
|||
removeSourceBranch() {
|
||||
this.isMakingRequest = true;
|
||||
this.service.removeSourceBranch()
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (res.message === 'Branch was removed') {
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
if (data.message === 'Branch was removed') {
|
||||
eventHub.$emit('MRWidgetUpdateRequested', () => {
|
||||
this.isMakingRequest = false;
|
||||
});
|
||||
|
|
@ -68,9 +68,9 @@ export default {
|
|||
<div class="space-children">
|
||||
<mr-widget-author-and-time
|
||||
actionText="Merged by"
|
||||
:author="mr.mergedEvent.author"
|
||||
:date-title="mr.mergedEvent.updatedAt"
|
||||
:date-readable="mr.mergedEvent.formattedUpdatedAt" />
|
||||
:author="mr.metrics.mergedBy"
|
||||
:date-title="mr.metrics.mergedAt"
|
||||
:date-readable="mr.metrics.readableMergedAt" />
|
||||
<a
|
||||
v-if="mr.canRevertInCurrentMR"
|
||||
v-tooltip
|
||||
|
|
|
|||
|
|
@ -135,16 +135,16 @@ export default {
|
|||
|
||||
this.isMakingRequest = true;
|
||||
this.service.merge(options)
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
const hasError = res.status === 'failed' || res.status === 'hook_validation_error';
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
|
||||
|
||||
if (res.status === 'merge_when_pipeline_succeeds') {
|
||||
if (data.status === 'merge_when_pipeline_succeeds') {
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
} else if (res.status === 'success') {
|
||||
} else if (data.status === 'success') {
|
||||
this.initiateMergePolling();
|
||||
} else if (hasError) {
|
||||
eventHub.$emit('FailedToMerge', res.merge_error);
|
||||
eventHub.$emit('FailedToMerge', data.merge_error);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -159,9 +159,9 @@ export default {
|
|||
},
|
||||
handleMergePolling(continuePolling, stopPolling) {
|
||||
this.service.poll()
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (res.state === 'merged') {
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
if (data.state === 'merged') {
|
||||
// If state is merged we should update the widget and stop the polling
|
||||
eventHub.$emit('MRWidgetUpdateRequested');
|
||||
eventHub.$emit('FetchActionsContent');
|
||||
|
|
@ -174,11 +174,11 @@ export default {
|
|||
|
||||
// If user checked remove source branch and we didn't remove the branch yet
|
||||
// we should start another polling for source branch remove process
|
||||
if (this.removeSourceBranch && res.source_branch_exists) {
|
||||
if (this.removeSourceBranch && data.source_branch_exists) {
|
||||
this.initiateRemoveSourceBranchPolling();
|
||||
}
|
||||
} else if (res.merge_error) {
|
||||
eventHub.$emit('FailedToMerge', res.merge_error);
|
||||
} else if (data.merge_error) {
|
||||
eventHub.$emit('FailedToMerge', data.merge_error);
|
||||
stopPolling();
|
||||
} else {
|
||||
// MR is not merged yet, continue polling until the state becomes 'merged'
|
||||
|
|
@ -199,11 +199,11 @@ export default {
|
|||
},
|
||||
handleRemoveBranchPolling(continuePolling, stopPolling) {
|
||||
this.service.poll()
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
// If source branch exists then we should continue polling
|
||||
// because removing a source branch is a background task and takes time
|
||||
if (res.source_branch_exists) {
|
||||
if (data.source_branch_exists) {
|
||||
continuePolling();
|
||||
} else {
|
||||
// Branch is removed. Update widget, stop polling and hide the spinner
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ export default {
|
|||
removeWIP() {
|
||||
this.isMakingRequest = true;
|
||||
this.service.removeWIP()
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
eventHub.$emit('UpdateWidgetData', res);
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
eventHub.$emit('UpdateWidgetData', data);
|
||||
new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line
|
||||
$('.merge-request .detail-page-description .title').text(this.mr.title);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -84,14 +84,14 @@ export default {
|
|||
},
|
||||
checkStatus(cb) {
|
||||
return this.service.checkStatus()
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
this.handleNotification(res);
|
||||
this.mr.setData(res);
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.handleNotification(data);
|
||||
this.mr.setData(data);
|
||||
this.setFaviconHelper();
|
||||
|
||||
if (cb) {
|
||||
cb.call(null, res);
|
||||
cb.call(null, data);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -124,10 +124,10 @@ export default {
|
|||
},
|
||||
fetchDeployments() {
|
||||
return this.service.fetchDeployments()
|
||||
.then(res => res.json())
|
||||
.then((res) => {
|
||||
if (res.length) {
|
||||
this.mr.deployments = res;
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
if (data.length) {
|
||||
this.mr.deployments = data;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -137,9 +137,9 @@ export default {
|
|||
fetchActionsContent() {
|
||||
this.service.fetchMergeActionsContent()
|
||||
.then((res) => {
|
||||
if (res.body) {
|
||||
if (res.data) {
|
||||
const el = document.createElement('div');
|
||||
el.innerHTML = res.body;
|
||||
el.innerHTML = res.data;
|
||||
document.body.appendChild(el);
|
||||
Project.initRefSwitcher();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,47 @@
|
|||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
|
||||
Vue.use(VueResource);
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
|
||||
export default class MRWidgetService {
|
||||
constructor(endpoints) {
|
||||
this.mergeResource = Vue.resource(endpoints.mergePath);
|
||||
this.mergeCheckResource = Vue.resource(`${endpoints.statusPath}?serializer=widget`);
|
||||
this.cancelAutoMergeResource = Vue.resource(endpoints.cancelAutoMergePath);
|
||||
this.removeWIPResource = Vue.resource(endpoints.removeWIPPath);
|
||||
this.removeSourceBranchResource = Vue.resource(endpoints.sourceBranchPath);
|
||||
this.deploymentsResource = Vue.resource(endpoints.ciEnvironmentsStatusPath);
|
||||
this.pollResource = Vue.resource(`${endpoints.statusPath}?serializer=basic`);
|
||||
this.mergeActionsContentResource = Vue.resource(endpoints.mergeActionsContentPath);
|
||||
this.endpoints = endpoints;
|
||||
}
|
||||
|
||||
merge(data) {
|
||||
return this.mergeResource.save(data);
|
||||
return axios.post(this.endpoints.mergePath, data);
|
||||
}
|
||||
|
||||
cancelAutomaticMerge() {
|
||||
return this.cancelAutoMergeResource.save();
|
||||
return axios.post(this.endpoints.cancelAutoMergePath);
|
||||
}
|
||||
|
||||
removeWIP() {
|
||||
return this.removeWIPResource.save();
|
||||
return axios.post(this.endpoints.removeWIPPath);
|
||||
}
|
||||
|
||||
removeSourceBranch() {
|
||||
return this.removeSourceBranchResource.delete();
|
||||
return axios.delete(this.endpoints.sourceBranchPath);
|
||||
}
|
||||
|
||||
fetchDeployments() {
|
||||
return this.deploymentsResource.get();
|
||||
return axios.get(this.endpoints.ciEnvironmentsStatusPath);
|
||||
}
|
||||
|
||||
poll() {
|
||||
return this.pollResource.get();
|
||||
return axios.get(`${this.endpoints.statusPath}?serializer=basic`);
|
||||
}
|
||||
|
||||
checkStatus() {
|
||||
return this.mergeCheckResource.get();
|
||||
return axios.get(`${this.endpoints.statusPath}?serializer=widget`);
|
||||
}
|
||||
|
||||
fetchMergeActionsContent() {
|
||||
return this.mergeActionsContentResource.get();
|
||||
return axios.get(this.endpoints.mergeActionsContentPath);
|
||||
}
|
||||
|
||||
static stopEnvironment(url) {
|
||||
return Vue.http.post(url);
|
||||
return axios.post(url);
|
||||
}
|
||||
|
||||
static fetchMetrics(metricsUrl) {
|
||||
return Vue.http.get(`${metricsUrl}.json`);
|
||||
return axios.get(`${metricsUrl}.json`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,9 +39,8 @@ export default class MergeRequestStore {
|
|||
}
|
||||
|
||||
this.updatedAt = data.updated_at;
|
||||
this.mergedEvent = MergeRequestStore.getEventObject(data.merge_event);
|
||||
this.closedEvent = MergeRequestStore.getEventObject(data.closed_event);
|
||||
this.setToMWPSBy = MergeRequestStore.getAuthorObject({ author: data.merge_user || {} });
|
||||
this.metrics = MergeRequestStore.buildMetrics(data.metrics);
|
||||
this.setToMWPSBy = MergeRequestStore.formatUserObject(data.merge_user || {});
|
||||
this.mergeUserId = data.merge_user_id;
|
||||
this.currentUserId = gon.current_user_id;
|
||||
this.sourceBranchPath = data.source_branch_path;
|
||||
|
|
@ -125,43 +124,42 @@ export default class MergeRequestStore {
|
|||
return this.state === stateKey.nothingToMerge;
|
||||
}
|
||||
|
||||
static getEventObject(event) {
|
||||
return {
|
||||
author: MergeRequestStore.getAuthorObject(event),
|
||||
updatedAt: formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
|
||||
formattedUpdatedAt: MergeRequestStore.getEventDate(event),
|
||||
};
|
||||
}
|
||||
|
||||
static getAuthorObject(event) {
|
||||
if (!event) {
|
||||
static buildMetrics(metrics) {
|
||||
if (!metrics) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
name: event.author.name || '',
|
||||
username: event.author.username || '',
|
||||
webUrl: event.author.web_url || '',
|
||||
avatarUrl: event.author.avatar_url || '',
|
||||
mergedBy: MergeRequestStore.formatUserObject(metrics.merged_by),
|
||||
closedBy: MergeRequestStore.formatUserObject(metrics.closed_by),
|
||||
mergedAt: formatDate(metrics.merged_at),
|
||||
closedAt: formatDate(metrics.closed_at),
|
||||
readableMergedAt: MergeRequestStore.getReadableDate(metrics.merged_at),
|
||||
readableClosedAt: MergeRequestStore.getReadableDate(metrics.closed_at),
|
||||
};
|
||||
}
|
||||
|
||||
static getEventUpdatedAtDate(event) {
|
||||
if (!event) {
|
||||
return '';
|
||||
static formatUserObject(user) {
|
||||
if (!user) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return event.updated_at;
|
||||
return {
|
||||
name: user.name || '',
|
||||
username: user.username || '',
|
||||
webUrl: user.web_url || '',
|
||||
avatarUrl: user.avatar_url || '',
|
||||
};
|
||||
}
|
||||
|
||||
static getEventDate(event) {
|
||||
const timeagoInstance = new Timeago();
|
||||
|
||||
if (!event) {
|
||||
static getReadableDate(date) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return timeagoInstance.format(MergeRequestStore.getEventUpdatedAtDate(event));
|
||||
const timeagoInstance = new Timeago();
|
||||
|
||||
return timeagoInstance.format(date);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
<script>
|
||||
import getIconForFile from './file_icon/file_icon_map';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
|
||||
/* This is a re-usable vue component for rendering a svg sprite
|
||||
icon
|
||||
|
||||
Sample configuration:
|
||||
|
||||
<file-icon
|
||||
name="retry"
|
||||
:size="32"
|
||||
css-classes="top"
|
||||
/>
|
||||
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
fileName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
folder: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
opened: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
size: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 16,
|
||||
},
|
||||
|
||||
cssClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loadingIcon,
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
spriteHref() {
|
||||
const iconName = getIconForFile(this.fileName) || 'file';
|
||||
return `${gon.sprite_file_icons}#${iconName}`;
|
||||
},
|
||||
folderIconName() {
|
||||
// We don't have a open folder icon yet
|
||||
return this.opened ? 'folder' : 'folder';
|
||||
},
|
||||
iconSizeClass() {
|
||||
return this.size ? `s${this.size}` : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<span>
|
||||
<svg
|
||||
:class="[iconSizeClass, cssClasses]"
|
||||
v-if="!loading && !folder">
|
||||
<use
|
||||
v-bind="{'xlink:href':spriteHref}"/>
|
||||
</svg>
|
||||
<icon
|
||||
v-if="!loading && folder"
|
||||
:name="folderIconName"
|
||||
:size="size"
|
||||
/>
|
||||
<loading-icon
|
||||
v-if="loading"
|
||||
:inline="true"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,589 @@
|
|||
const fileExtensionIcons = {
|
||||
html: 'html',
|
||||
htm: 'html',
|
||||
html_vm: 'html',
|
||||
asp: 'html',
|
||||
jade: 'pug',
|
||||
pug: 'pug',
|
||||
md: 'markdown',
|
||||
'md.rendered': 'markdown',
|
||||
markdown: 'markdown',
|
||||
'markdown.rendered': 'markdown',
|
||||
rst: 'markdown',
|
||||
blink: 'blink',
|
||||
css: 'css',
|
||||
scss: 'sass',
|
||||
sass: 'sass',
|
||||
less: 'less',
|
||||
json: 'json',
|
||||
yaml: 'yaml',
|
||||
'YAML-tmLanguage': 'yaml',
|
||||
yml: 'yaml',
|
||||
xml: 'xml',
|
||||
plist: 'xml',
|
||||
xsd: 'xml',
|
||||
dtd: 'xml',
|
||||
xsl: 'xml',
|
||||
xslt: 'xml',
|
||||
resx: 'xml',
|
||||
iml: 'xml',
|
||||
xquery: 'xml',
|
||||
tmLanguage: 'xml',
|
||||
manifest: 'xml',
|
||||
project: 'xml',
|
||||
png: 'image',
|
||||
jpeg: 'image',
|
||||
jpg: 'image',
|
||||
gif: 'image',
|
||||
svg: 'image',
|
||||
ico: 'image',
|
||||
tif: 'image',
|
||||
tiff: 'image',
|
||||
psd: 'image',
|
||||
psb: 'image',
|
||||
ami: 'image',
|
||||
apx: 'image',
|
||||
bmp: 'image',
|
||||
bpg: 'image',
|
||||
brk: 'image',
|
||||
cur: 'image',
|
||||
dds: 'image',
|
||||
dng: 'image',
|
||||
exr: 'image',
|
||||
fpx: 'image',
|
||||
gbr: 'image',
|
||||
img: 'image',
|
||||
jbig2: 'image',
|
||||
jb2: 'image',
|
||||
jng: 'image',
|
||||
jxr: 'image',
|
||||
pbm: 'image',
|
||||
pgf: 'image',
|
||||
pic: 'image',
|
||||
raw: 'image',
|
||||
webp: 'image',
|
||||
js: 'javascript',
|
||||
ejs: 'javascript',
|
||||
esx: 'javascript',
|
||||
jsx: 'react',
|
||||
tsx: 'react',
|
||||
ini: 'settings',
|
||||
dlc: 'settings',
|
||||
dll: 'settings',
|
||||
config: 'settings',
|
||||
conf: 'settings',
|
||||
properties: 'settings',
|
||||
prop: 'settings',
|
||||
settings: 'settings',
|
||||
option: 'settings',
|
||||
props: 'settings',
|
||||
toml: 'settings',
|
||||
prefs: 'settings',
|
||||
'sln.dotsettings': 'settings',
|
||||
'sln.dotsettings.user': 'settings',
|
||||
ts: 'typescript',
|
||||
'd.ts': 'typescript-def',
|
||||
marko: 'markojs',
|
||||
pdf: 'pdf',
|
||||
xlsx: 'table',
|
||||
xls: 'table',
|
||||
csv: 'table',
|
||||
tsv: 'table',
|
||||
vscodeignore: 'vscode',
|
||||
vsixmanifest: 'vscode',
|
||||
vsix: 'vscode',
|
||||
'code-workplace': 'vscode',
|
||||
suo: 'visualstudio',
|
||||
sln: 'visualstudio',
|
||||
csproj: 'visualstudio',
|
||||
vb: 'visualstudio',
|
||||
pdb: 'database',
|
||||
sql: 'database',
|
||||
pks: 'database',
|
||||
pkb: 'database',
|
||||
accdb: 'database',
|
||||
mdb: 'database',
|
||||
sqlite: 'database',
|
||||
cs: 'csharp',
|
||||
zip: 'zip',
|
||||
tar: 'zip',
|
||||
gz: 'zip',
|
||||
xz: 'zip',
|
||||
bzip2: 'zip',
|
||||
gzip: 'zip',
|
||||
'7z': 'zip',
|
||||
rar: 'zip',
|
||||
tgz: 'zip',
|
||||
exe: 'exe',
|
||||
msi: 'exe',
|
||||
java: 'java',
|
||||
jar: 'java',
|
||||
jsp: 'java',
|
||||
c: 'c',
|
||||
m: 'c',
|
||||
h: 'h',
|
||||
cc: 'cpp',
|
||||
cpp: 'cpp',
|
||||
mm: 'cpp',
|
||||
cxx: 'cpp',
|
||||
hpp: 'hpp',
|
||||
go: 'go',
|
||||
py: 'python',
|
||||
url: 'url',
|
||||
sh: 'console',
|
||||
ksh: 'console',
|
||||
csh: 'console',
|
||||
tcsh: 'console',
|
||||
zsh: 'console',
|
||||
bash: 'console',
|
||||
bat: 'console',
|
||||
cmd: 'console',
|
||||
ps1: 'powershell',
|
||||
psm1: 'powershell',
|
||||
psd1: 'powershell',
|
||||
ps1xml: 'powershell',
|
||||
psc1: 'powershell',
|
||||
pssc: 'powershell',
|
||||
gradle: 'gradle',
|
||||
doc: 'word',
|
||||
docx: 'word',
|
||||
rtf: 'word',
|
||||
cer: 'certificate',
|
||||
cert: 'certificate',
|
||||
crt: 'certificate',
|
||||
pub: 'key',
|
||||
key: 'key',
|
||||
pem: 'key',
|
||||
asc: 'key',
|
||||
gpg: 'key',
|
||||
woff: 'font',
|
||||
woff2: 'font',
|
||||
ttf: 'font',
|
||||
eot: 'font',
|
||||
suit: 'font',
|
||||
otf: 'font',
|
||||
bmap: 'font',
|
||||
fnt: 'font',
|
||||
odttf: 'font',
|
||||
ttc: 'font',
|
||||
font: 'font',
|
||||
fonts: 'font',
|
||||
sui: 'font',
|
||||
ntf: 'font',
|
||||
mrf: 'font',
|
||||
lib: 'lib',
|
||||
bib: 'lib',
|
||||
rb: 'ruby',
|
||||
erb: 'ruby',
|
||||
fs: 'fsharp',
|
||||
fsx: 'fsharp',
|
||||
fsi: 'fsharp',
|
||||
fsproj: 'fsharp',
|
||||
swift: 'swift',
|
||||
ino: 'arduino',
|
||||
dockerignore: 'docker',
|
||||
dockerfile: 'docker',
|
||||
tex: 'tex',
|
||||
cls: 'tex',
|
||||
sty: 'tex',
|
||||
pptx: 'powerpoint',
|
||||
ppt: 'powerpoint',
|
||||
pptm: 'powerpoint',
|
||||
potx: 'powerpoint',
|
||||
pot: 'powerpoint',
|
||||
potm: 'powerpoint',
|
||||
ppsx: 'powerpoint',
|
||||
ppsm: 'powerpoint',
|
||||
pps: 'powerpoint',
|
||||
ppam: 'powerpoint',
|
||||
ppa: 'powerpoint',
|
||||
webm: 'movie',
|
||||
mkv: 'movie',
|
||||
flv: 'movie',
|
||||
vob: 'movie',
|
||||
ogv: 'movie',
|
||||
ogg: 'movie',
|
||||
gifv: 'movie',
|
||||
avi: 'movie',
|
||||
mov: 'movie',
|
||||
qt: 'movie',
|
||||
wmv: 'movie',
|
||||
yuv: 'movie',
|
||||
rm: 'movie',
|
||||
rmvb: 'movie',
|
||||
mp4: 'movie',
|
||||
m4v: 'movie',
|
||||
mpg: 'movie',
|
||||
mp2: 'movie',
|
||||
mpeg: 'movie',
|
||||
mpe: 'movie',
|
||||
mpv: 'movie',
|
||||
m2v: 'movie',
|
||||
vdi: 'virtual',
|
||||
vbox: 'virtual',
|
||||
'vbox-prev': 'virtual',
|
||||
ics: 'email',
|
||||
mp3: 'music',
|
||||
flac: 'music',
|
||||
m4a: 'music',
|
||||
wma: 'music',
|
||||
aiff: 'music',
|
||||
coffee: 'coffee',
|
||||
txt: 'document',
|
||||
graphql: 'graphql',
|
||||
rs: 'rust',
|
||||
raml: 'raml',
|
||||
xaml: 'xaml',
|
||||
hs: 'haskell',
|
||||
kt: 'kotlin',
|
||||
kts: 'kotlin',
|
||||
patch: 'git',
|
||||
lua: 'lua',
|
||||
clj: 'clojure',
|
||||
cljs: 'clojure',
|
||||
groovy: 'groovy',
|
||||
r: 'r',
|
||||
rmd: 'r',
|
||||
dart: 'dart',
|
||||
as: 'actionscript',
|
||||
mxml: 'mxml',
|
||||
ahk: 'autohotkey',
|
||||
swf: 'flash',
|
||||
swc: 'swc',
|
||||
cmake: 'cmake',
|
||||
asm: 'assembly',
|
||||
a51: 'assembly',
|
||||
inc: 'assembly',
|
||||
nasm: 'assembly',
|
||||
s: 'assembly',
|
||||
ms: 'assembly',
|
||||
agc: 'assembly',
|
||||
ags: 'assembly',
|
||||
aea: 'assembly',
|
||||
argus: 'assembly',
|
||||
mitigus: 'assembly',
|
||||
binsource: 'assembly',
|
||||
vue: 'vue',
|
||||
ml: 'ocaml',
|
||||
mli: 'ocaml',
|
||||
cmx: 'ocaml',
|
||||
'js.map': 'javascript-map',
|
||||
'css.map': 'css-map',
|
||||
lock: 'lock',
|
||||
hbs: 'handlebars',
|
||||
mustache: 'handlebars',
|
||||
pl: 'perl',
|
||||
pm: 'perl',
|
||||
hx: 'haxe',
|
||||
'spec.ts': 'test-ts',
|
||||
'test.ts': 'test-ts',
|
||||
'ts.snap': 'test-ts',
|
||||
'spec.tsx': 'test-jsx',
|
||||
'test.tsx': 'test-jsx',
|
||||
'tsx.snap': 'test-jsx',
|
||||
'spec.jsx': 'test-jsx',
|
||||
'test.jsx': 'test-jsx',
|
||||
'jsx.snap': 'test-jsx',
|
||||
'spec.js': 'test-js',
|
||||
'test.js': 'test-js',
|
||||
'js.snap': 'test-js',
|
||||
'routing.ts': 'angular-routing',
|
||||
'routing.js': 'angular-routing',
|
||||
'module.ts': 'angular',
|
||||
'module.js': 'angular',
|
||||
'ng-template': 'angular',
|
||||
'component.ts': 'angular-component',
|
||||
'component.js': 'angular-component',
|
||||
'guard.ts': 'angular-guard',
|
||||
'guard.js': 'angular-guard',
|
||||
'service.ts': 'angular-service',
|
||||
'service.js': 'angular-service',
|
||||
'pipe.ts': 'angular-pipe',
|
||||
'pipe.js': 'angular-pipe',
|
||||
'filter.js': 'angular-pipe',
|
||||
'directive.ts': 'angular-directive',
|
||||
'directive.js': 'angular-directive',
|
||||
'resolver.ts': 'angular-resolver',
|
||||
'resolver.js': 'angular-resolver',
|
||||
pp: 'puppet',
|
||||
ex: 'elixir',
|
||||
exs: 'elixir',
|
||||
ls: 'livescript',
|
||||
erl: 'erlang',
|
||||
twig: 'twig',
|
||||
jl: 'julia',
|
||||
elm: 'elm',
|
||||
pure: 'purescript',
|
||||
tpl: 'smarty',
|
||||
styl: 'stylus',
|
||||
re: 'reason',
|
||||
rei: 'reason',
|
||||
cmj: 'bucklescript',
|
||||
merlin: 'merlin',
|
||||
v: 'verilog',
|
||||
vhd: 'verilog',
|
||||
sv: 'verilog',
|
||||
svh: 'verilog',
|
||||
nb: 'mathematica',
|
||||
wl: 'wolframlanguage',
|
||||
wls: 'wolframlanguage',
|
||||
njk: 'nunjucks',
|
||||
nunjucks: 'nunjucks',
|
||||
robot: 'robot',
|
||||
sol: 'solidity',
|
||||
au3: 'autoit',
|
||||
haml: 'haml',
|
||||
yang: 'yang',
|
||||
tf: 'terraform',
|
||||
'tf.json': 'terraform',
|
||||
tfvars: 'terraform',
|
||||
tfstate: 'terraform',
|
||||
'blade.php': 'laravel',
|
||||
'inky.php': 'laravel',
|
||||
applescript: 'applescript',
|
||||
cake: 'cake',
|
||||
feature: 'cucumber',
|
||||
nim: 'nim',
|
||||
nimble: 'nim',
|
||||
apib: 'apiblueprint',
|
||||
apiblueprint: 'apiblueprint',
|
||||
tag: 'riot',
|
||||
vfl: 'vfl',
|
||||
kl: 'kl',
|
||||
pcss: 'postcss',
|
||||
sss: 'postcss',
|
||||
todo: 'todo',
|
||||
cfml: 'coldfusion',
|
||||
cfc: 'coldfusion',
|
||||
lucee: 'coldfusion',
|
||||
cabal: 'cabal',
|
||||
nix: 'nix',
|
||||
slim: 'slim',
|
||||
http: 'http',
|
||||
rest: 'http',
|
||||
rql: 'restql',
|
||||
restql: 'restql',
|
||||
kv: 'kivy',
|
||||
graphcool: 'graphcool',
|
||||
sbt: 'sbt',
|
||||
'reducer.ts': 'ngrx-reducer',
|
||||
'rootReducer.ts': 'ngrx-reducer',
|
||||
'state.ts': 'ngrx-state',
|
||||
'actions.ts': 'ngrx-actions',
|
||||
'effects.ts': 'ngrx-effects',
|
||||
cr: 'crystal',
|
||||
'drone.yml': 'drone',
|
||||
cu: 'cuda',
|
||||
cuh: 'cuda',
|
||||
log: 'log',
|
||||
};
|
||||
|
||||
const fileNameIcons = {
|
||||
'.jscsrc': 'json',
|
||||
'.jshintrc': 'json',
|
||||
'tsconfig.json': 'json',
|
||||
'tslint.json': 'json',
|
||||
'composer.lock': 'json',
|
||||
'.jsbeautifyrc': 'json',
|
||||
'.esformatter': 'json',
|
||||
'cdp.pid': 'json',
|
||||
'.htaccess': 'xml',
|
||||
'.jshintignore': 'settings',
|
||||
'.buildignore': 'settings',
|
||||
makefile: 'settings',
|
||||
'.mrconfig': 'settings',
|
||||
'.yardopts': 'settings',
|
||||
'gradle.properties': 'gradle',
|
||||
gradlew: 'gradle',
|
||||
'gradle-wrapper.properties': 'gradle',
|
||||
license: 'certificate',
|
||||
'license.md': 'certificate',
|
||||
'license.md.rendered': 'certificate',
|
||||
'license.txt': 'certificate',
|
||||
licence: 'certificate',
|
||||
'licence.md': 'certificate',
|
||||
'licence.md.rendered': 'certificate',
|
||||
'licence.txt': 'certificate',
|
||||
dockerfile: 'docker',
|
||||
'docker-compose.yml': 'docker',
|
||||
'.mailmap': 'email',
|
||||
'.gitignore': 'git',
|
||||
'.gitconfig': 'git',
|
||||
'.gitattributes': 'git',
|
||||
'.gitmodules': 'git',
|
||||
'.gitkeep': 'git',
|
||||
'git-history': 'git',
|
||||
'.Rhistory': 'r',
|
||||
'cmakelists.txt': 'cmake',
|
||||
'cmakecache.txt': 'cmake',
|
||||
'angular-cli.json': 'angular',
|
||||
'.angular-cli.json': 'angular',
|
||||
'.vfl': 'vfl',
|
||||
'.kl': 'kl',
|
||||
'postcss.config.js': 'postcss',
|
||||
'.postcssrc.js': 'postcss',
|
||||
'project.graphcool': 'graphcool',
|
||||
'webpack.js': 'webpack',
|
||||
'webpack.ts': 'webpack',
|
||||
'webpack.base.js': 'webpack',
|
||||
'webpack.base.ts': 'webpack',
|
||||
'webpack.config.js': 'webpack',
|
||||
'webpack.config.ts': 'webpack',
|
||||
'webpack.common.js': 'webpack',
|
||||
'webpack.common.ts': 'webpack',
|
||||
'webpack.config.common.js': 'webpack',
|
||||
'webpack.config.common.ts': 'webpack',
|
||||
'webpack.config.common.babel.js': 'webpack',
|
||||
'webpack.config.common.babel.ts': 'webpack',
|
||||
'webpack.dev.js': 'webpack',
|
||||
'webpack.dev.ts': 'webpack',
|
||||
'webpack.config.dev.js': 'webpack',
|
||||
'webpack.config.dev.ts': 'webpack',
|
||||
'webpack.config.dev.babel.js': 'webpack',
|
||||
'webpack.config.dev.babel.ts': 'webpack',
|
||||
'webpack.prod.js': 'webpack',
|
||||
'webpack.prod.ts': 'webpack',
|
||||
'webpack.server.js': 'webpack',
|
||||
'webpack.server.ts': 'webpack',
|
||||
'webpack.client.js': 'webpack',
|
||||
'webpack.client.ts': 'webpack',
|
||||
'webpack.config.server.js': 'webpack',
|
||||
'webpack.config.server.ts': 'webpack',
|
||||
'webpack.config.client.js': 'webpack',
|
||||
'webpack.config.client.ts': 'webpack',
|
||||
'webpack.config.production.babel.js': 'webpack',
|
||||
'webpack.config.production.babel.ts': 'webpack',
|
||||
'webpack.config.prod.babel.js': 'webpack',
|
||||
'webpack.config.prod.babel.ts': 'webpack',
|
||||
'webpack.config.prod.js': 'webpack',
|
||||
'webpack.config.prod.ts': 'webpack',
|
||||
'webpack.config.production.js': 'webpack',
|
||||
'webpack.config.production.ts': 'webpack',
|
||||
'webpack.config.staging.js': 'webpack',
|
||||
'webpack.config.staging.ts': 'webpack',
|
||||
'webpack.config.babel.js': 'webpack',
|
||||
'webpack.config.babel.ts': 'webpack',
|
||||
'webpack.config.base.babel.js': 'webpack',
|
||||
'webpack.config.base.babel.ts': 'webpack',
|
||||
'webpack.config.base.js': 'webpack',
|
||||
'webpack.config.base.ts': 'webpack',
|
||||
'webpack.config.staging.babel.js': 'webpack',
|
||||
'webpack.config.staging.babel.ts': 'webpack',
|
||||
'webpack.config.coffee': 'webpack',
|
||||
'webpack.config.test.js': 'webpack',
|
||||
'webpack.config.test.ts': 'webpack',
|
||||
'webpack.config.vendor.js': 'webpack',
|
||||
'webpack.config.vendor.ts': 'webpack',
|
||||
'webpack.config.vendor.production.js': 'webpack',
|
||||
'webpack.config.vendor.production.ts': 'webpack',
|
||||
'webpack.test.js': 'webpack',
|
||||
'webpack.test.ts': 'webpack',
|
||||
'webpack.dist.js': 'webpack',
|
||||
'webpack.dist.ts': 'webpack',
|
||||
'webpackfile.js': 'webpack',
|
||||
'webpackfile.ts': 'webpack',
|
||||
'ionic.config.json': 'ionic',
|
||||
'.io-config.json': 'ionic',
|
||||
'gulpfile.js': 'gulp',
|
||||
'gulpfile.ts': 'gulp',
|
||||
'gulpfile.babel.js': 'gulp',
|
||||
'package.json': 'nodejs',
|
||||
'package-lock.json': 'nodejs',
|
||||
'.nvmrc': 'nodejs',
|
||||
'.npmignore': 'npm',
|
||||
'.npmrc': 'npm',
|
||||
'.yarnrc': 'yarn',
|
||||
'yarn.lock': 'yarn',
|
||||
'.yarnclean': 'yarn',
|
||||
'.yarn-integrity': 'yarn',
|
||||
'yarn-error.log': 'yarn',
|
||||
'androidmanifest.xml': 'android',
|
||||
'.env': 'tune',
|
||||
'.env.example': 'tune',
|
||||
'.babelrc': 'babel',
|
||||
'contributing.md': 'contributing',
|
||||
'contributing.md.rendered': 'contributing',
|
||||
'readme.md': 'readme',
|
||||
'readme.md.rendered': 'readme',
|
||||
changelog: 'changelog',
|
||||
'changelog.md': 'changelog',
|
||||
'changelog.md.rendered': 'changelog',
|
||||
CREDITS: 'credits',
|
||||
'credits.txt': 'credits',
|
||||
'credits.md': 'credits',
|
||||
'credits.md.rendered': 'credits',
|
||||
'.flowconfig': 'flow',
|
||||
'favicon.ico': 'favicon',
|
||||
'karma.conf.js': 'karma',
|
||||
'karma.conf.ts': 'karma',
|
||||
'karma.conf.coffee': 'karma',
|
||||
'karma.config.js': 'karma',
|
||||
'karma.config.ts': 'karma',
|
||||
'karma-main.js': 'karma',
|
||||
'karma-main.ts': 'karma',
|
||||
'.bithoundrc': 'bithound',
|
||||
'appveyor.yml': 'appveyor',
|
||||
'.travis.yml': 'travis',
|
||||
'protractor.conf.js': 'protractor',
|
||||
'protractor.conf.ts': 'protractor',
|
||||
'protractor.conf.coffee': 'protractor',
|
||||
'protractor.config.js': 'protractor',
|
||||
'protractor.config.ts': 'protractor',
|
||||
'fuse.js': 'fusebox',
|
||||
procfile: 'heroku',
|
||||
'.editorconfig': 'editorconfig',
|
||||
'.gitlab-ci.yml': 'gitlab',
|
||||
'.bowerrc': 'bower',
|
||||
'bower.json': 'bower',
|
||||
'.eslintrc.js': 'eslint',
|
||||
'.eslintrc.yaml': 'eslint',
|
||||
'.eslintrc.yml': 'eslint',
|
||||
'.eslintrc.json': 'eslint',
|
||||
'.eslintrc': 'eslint',
|
||||
'.eslintignore': 'eslint',
|
||||
'code_of_conduct.md': 'conduct',
|
||||
'code_of_conduct.md.rendered': 'conduct',
|
||||
'.watchmanconfig': 'watchman',
|
||||
'aurelia.json': 'aurelia',
|
||||
'mocha.opts': 'mocha',
|
||||
jenkinsfile: 'jenkins',
|
||||
'firebase.json': 'firebase',
|
||||
'.firebaserc': 'firebase',
|
||||
'rollup.config.js': 'rollup',
|
||||
'rollup.config.ts': 'rollup',
|
||||
'rollup-config.js': 'rollup',
|
||||
'rollup-config.ts': 'rollup',
|
||||
'rollup.config.prod.js': 'rollup',
|
||||
'rollup.config.prod.ts': 'rollup',
|
||||
'rollup.config.dev.js': 'rollup',
|
||||
'rollup.config.dev.ts': 'rollup',
|
||||
'rollup.config.prod.vendor.js': 'rollup',
|
||||
'rollup.config.prod.vendor.ts': 'rollup',
|
||||
'.hhconfig': 'hack',
|
||||
'.stylelintrc': 'stylelint',
|
||||
'stylelint.config.js': 'stylelint',
|
||||
'.stylelintrc.json': 'stylelint',
|
||||
'.stylelintrc.yaml': 'stylelint',
|
||||
'.stylelintrc.yml': 'stylelint',
|
||||
'.stylelintrc.js': 'stylelint',
|
||||
'.stylelintignore': 'stylelint',
|
||||
'.codeclimate.yml': 'code-climate',
|
||||
'.prettierrc': 'prettier',
|
||||
'prettier.config.js': 'prettier',
|
||||
'.prettierrc.js': 'prettier',
|
||||
'.prettierrc.json': 'prettier',
|
||||
'.prettierrc.yaml': 'prettier',
|
||||
'.prettierrc.yml': 'prettier',
|
||||
'nodemon.json': 'nodemon',
|
||||
'.sonarrc': 'sonar',
|
||||
browserslist: 'browserlist',
|
||||
'.browserslistrc': 'browserlist',
|
||||
'.snyk': 'snyk',
|
||||
'.drone.yml': 'drone',
|
||||
};
|
||||
|
||||
export default function getIconForFile(name) {
|
||||
return fileNameIcons[name] ||
|
||||
fileExtensionIcons[name ? name.split('.').pop() : ''] ||
|
||||
'';
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
startSize: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
side: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
minSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
maxSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: Number.MAX_VALUE,
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
size: this.startSize,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
className() {
|
||||
return `drag${this.side}`;
|
||||
},
|
||||
cursorStyle() {
|
||||
if (this.enabled) {
|
||||
return { cursor: 'ew-resize' };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetSize(e) {
|
||||
e.preventDefault();
|
||||
this.size = this.startSize;
|
||||
this.$emit('update:size', this.size);
|
||||
},
|
||||
startDrag(e) {
|
||||
if (this.enabled) {
|
||||
e.preventDefault();
|
||||
this.startPos = e.clientX;
|
||||
this.currentStartSize = this.size;
|
||||
document.addEventListener('mousemove', this.drag);
|
||||
document.addEventListener('mouseup', this.endDrag, { once: true });
|
||||
this.$emit('resize-start', this.size);
|
||||
}
|
||||
},
|
||||
drag(e) {
|
||||
e.preventDefault();
|
||||
let moved = e.clientX - this.startPos;
|
||||
if (this.side === 'left') moved = -moved;
|
||||
let newSize = this.currentStartSize + moved;
|
||||
if (newSize < this.minSize) {
|
||||
newSize = this.minSize;
|
||||
} else if (newSize > this.maxSize) {
|
||||
newSize = this.maxSize;
|
||||
}
|
||||
this.size = newSize;
|
||||
|
||||
this.$emit('update:size', newSize);
|
||||
},
|
||||
endDrag(e) {
|
||||
e.preventDefault();
|
||||
document.removeEventListener('mousemove', this.drag);
|
||||
this.$emit('resize-end', this.size);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="dragHandle"
|
||||
:class="className"
|
||||
:style="cursorStyle"
|
||||
@mousedown="startDrag"
|
||||
@dblclick="resetSize"
|
||||
></div>
|
||||
</template>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
vertical-align: top;
|
||||
|
||||
&.s16 { font-size: 12px; line-height: 1.33; }
|
||||
&.s24 { font-size: 14px; line-height: 1.8; }
|
||||
&.s24 { font-size: 13px; line-height: 1.8; }
|
||||
&.s26 { font-size: 20px; line-height: 1.33; }
|
||||
&.s32 { font-size: 20px; line-height: 30px; }
|
||||
&.s40 { font-size: 16px; line-height: 38px; }
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
color: $gl-text-color;
|
||||
font-weight: $gl-font-weight-normal;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
|
||||
&.diff-collapsed {
|
||||
padding: 5px;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
.context-header {
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
width: $contextual-sidebar-width;
|
||||
|
||||
a {
|
||||
transition: padding $sidebar-transition-duration;
|
||||
|
|
@ -358,10 +359,6 @@
|
|||
}
|
||||
|
||||
.sidebar-top-level-items > li {
|
||||
&.active a {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.sidebar-sub-level-items {
|
||||
&:not(.flyout-list) {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@
|
|||
.header-user {
|
||||
.dropdown-menu-nav {
|
||||
width: auto;
|
||||
min-width: 140px;
|
||||
min-width: 160px;
|
||||
margin-top: 4px;
|
||||
color: $gl-text-color;
|
||||
left: auto;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
padding: 10px 15px;
|
||||
min-height: 20px;
|
||||
border-bottom: 1px solid $list-border;
|
||||
word-wrap: break-word;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
|
|
@ -125,10 +126,8 @@ ul.content-list {
|
|||
}
|
||||
|
||||
.description {
|
||||
p {
|
||||
@include str-truncated;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@include str-truncated;
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
.controls {
|
||||
|
|
@ -314,7 +313,7 @@ ul.indent-list {
|
|||
border: 2px solid $white-normal;
|
||||
|
||||
&.identicon {
|
||||
line-height: 30px;
|
||||
line-height: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -348,14 +347,19 @@ ul.indent-list {
|
|||
|
||||
.folder-caret {
|
||||
width: 15px;
|
||||
|
||||
svg {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-type-icon {
|
||||
margin-top: 2px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
> .group-row:not(.has-children) {
|
||||
.folder-caret .fa {
|
||||
.folder-caret {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -438,12 +442,61 @@ ul.indent-list {
|
|||
|
||||
.avatar-container > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.has-more-items {
|
||||
display: block;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
position: relative;
|
||||
line-height: 46px;
|
||||
|
||||
> span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 16px;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
> span:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
margin: 2px 0 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-left: 5px;
|
||||
|
||||
> .btn {
|
||||
margin-right: $btn-xs-side-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-row-contents .stats {
|
||||
line-height: inherit;
|
||||
|
||||
> span:first-child {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.item-visibility {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
min-width: 250px;
|
||||
text-align: right;
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -455,12 +508,12 @@ ul.indent-list {
|
|||
|
||||
ul.group-list-tree {
|
||||
li.group-row {
|
||||
&.has-description .title {
|
||||
line-height: inherit;
|
||||
> .group-row-contents .title {
|
||||
line-height: $list-text-height;
|
||||
}
|
||||
|
||||
&:not(.has-description) .title {
|
||||
line-height: $list-text-height;
|
||||
&.has-description > .group-row-contents .title {
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,6 +178,10 @@
|
|||
font-weight: inherit;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-left: $gl-padding;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -727,3 +727,8 @@ Popup
|
|||
$popup-triangle-size: 15px;
|
||||
$popup-triangle-border-size: 1px;
|
||||
$popup-box-shadow-color: rgba(90, 90, 90, 0.05);
|
||||
|
||||
/*
|
||||
Multi file editor
|
||||
*/
|
||||
$border-color-settings: #e1e1e1;
|
||||
|
|
|
|||
|
|
@ -159,7 +159,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Last push widget
|
||||
*/
|
||||
|
|
@ -182,6 +181,12 @@
|
|||
.event-item {
|
||||
padding-left: 0;
|
||||
|
||||
&.event-inline {
|
||||
.event-title {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.event-title {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.multi-file-editor-options {
|
||||
label {
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preview {
|
||||
font-size: 0;
|
||||
|
||||
img {
|
||||
border: 1px solid $border-color-settings;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.application-theme {
|
||||
label {
|
||||
margin-right: 20px;
|
||||
|
|
|
|||
|
|
@ -36,10 +36,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.with-performance-bar .ide-view {
|
||||
height: calc(100vh - #{$header-height});
|
||||
}
|
||||
|
||||
.ide-file-list {
|
||||
flex: 1;
|
||||
|
||||
|
|
@ -96,8 +92,14 @@
|
|||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.multi-file-table-name {
|
||||
table.table tr td.multi-file-table-name {
|
||||
width: 350px;
|
||||
padding: 6px 12px;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.multi-file-table-col-commit-message {
|
||||
|
|
@ -132,6 +134,10 @@
|
|||
border-bottom: 1px solid $white-dark;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $white-light;
|
||||
border-bottom-color: $white-light;
|
||||
|
|
@ -232,12 +238,13 @@
|
|||
|
||||
.multi-file-commit-panel {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 290px;
|
||||
padding: 0;
|
||||
background-color: $gray-light;
|
||||
border-left: 1px solid $white-dark;
|
||||
padding-right: 3px;
|
||||
|
||||
.projects-sidebar {
|
||||
display: flex;
|
||||
|
|
@ -486,3 +493,30 @@
|
|||
margin-top: $header-height;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.with-performance-bar {
|
||||
.ide-flash-container.flash-container {
|
||||
margin-top: $header-height + $performance-bar-height;
|
||||
}
|
||||
|
||||
.ide-view {
|
||||
height: calc(100vh - #{$header-height + $performance-bar-height});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.dragHandle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background-color: $white-dark;
|
||||
|
||||
&.dragright {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.dragleft {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,3 +268,7 @@
|
|||
margin: 0 0 5px 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.deprecated-service {
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
class PasswordsController < Devise::PasswordsController
|
||||
include Gitlab::CurrentSettings
|
||||
|
||||
skip_before_action :require_no_authentication, only: [:edit, :update]
|
||||
|
||||
before_action :resource_from_email, only: [:create]
|
||||
before_action :check_password_authentication_available, only: [:create]
|
||||
before_action :throttle_reset, only: [:create]
|
||||
|
|
|
|||
|
|
@ -46,14 +46,16 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
result = CreateBranchService.new(project, current_user)
|
||||
.execute(branch_name, ref)
|
||||
|
||||
if params[:issue_iid]
|
||||
success = (result[:status] == :success)
|
||||
|
||||
if params[:issue_iid] && success
|
||||
issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid])
|
||||
SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if result[:status] == :success
|
||||
if success
|
||||
if redirect_to_autodeploy
|
||||
redirect_to url_to_autodeploy_setup(project, branch_name),
|
||||
notice: view_context.autodeploy_flash_notice(branch_name)
|
||||
|
|
@ -67,7 +69,7 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
format.json do
|
||||
if result[:status] == :success
|
||||
if success
|
||||
render json: { name: branch_name, url: project_tree_url(@project, branch_name) }
|
||||
else
|
||||
render json: result[:messsage], status: :unprocessable_entity
|
||||
|
|
|
|||
|
|
@ -29,17 +29,17 @@ class Projects::RunnersController < Projects::ApplicationController
|
|||
|
||||
def resume
|
||||
if Ci::UpdateRunnerService.new(@runner).update(active: true)
|
||||
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
|
||||
redirect_to runners_path(@project), notice: 'Runner was successfully updated.'
|
||||
else
|
||||
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
|
||||
redirect_to runners_path(@project), alert: 'Runner was not updated.'
|
||||
end
|
||||
end
|
||||
|
||||
def pause
|
||||
if Ci::UpdateRunnerService.new(@runner).update(active: false)
|
||||
redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
|
||||
redirect_to runners_path(@project), notice: 'Runner was successfully updated.'
|
||||
else
|
||||
redirect_to runner_path(@runner), alert: 'Runner was not updated.'
|
||||
redirect_to runners_path(@project), alert: 'Runner was not updated.'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ module IconsHelper
|
|||
ActionController::Base.helpers.image_path('icons.svg', host: sprite_base_url)
|
||||
end
|
||||
|
||||
def sprite_file_icons_path
|
||||
# SVG Sprites currently don't work across domains, so in the case of a CDN
|
||||
# we have to set the current path deliberately to prevent addition of asset_host
|
||||
sprite_base_url = Gitlab.config.gitlab.url if ActionController::Base.asset_host
|
||||
ActionController::Base.helpers.image_path('file_icons.svg', host: sprite_base_url)
|
||||
end
|
||||
|
||||
def sprite_icon(icon_name, size: nil, css_class: nil)
|
||||
css_classes = size ? "s#{size}" : ""
|
||||
css_classes << " #{css_class}" unless css_class.blank?
|
||||
|
|
|
|||
|
|
@ -389,7 +389,7 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
|
||||
commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase }
|
||||
commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name }
|
||||
project_new_blob_path(
|
||||
project,
|
||||
project.default_branch || 'master',
|
||||
|
|
|
|||
|
|
@ -27,5 +27,16 @@ module ServicesHelper
|
|||
"#{event}_events"
|
||||
end
|
||||
|
||||
def service_save_button(service)
|
||||
button_tag(class: 'btn btn-save', type: 'submit', disabled: service.deprecated?) do
|
||||
icon('spinner spin', class: 'hidden js-btn-spinner') +
|
||||
content_tag(:span, 'Save changes', class: 'js-btn-label')
|
||||
end
|
||||
end
|
||||
|
||||
def disable_fields_service?(service)
|
||||
!current_controller?("admin/services") && service.deprecated?
|
||||
end
|
||||
|
||||
extend self
|
||||
end
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ module Issuable
|
|||
|
||||
strip_attributes :title
|
||||
|
||||
after_save :record_metrics, unless: :imported?
|
||||
after_save :ensure_metrics, unless: :imported?
|
||||
|
||||
# We want to use optimistic lock for cases when only title or description are involved
|
||||
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
|
||||
|
|
@ -335,11 +335,6 @@ module Issuable
|
|||
false
|
||||
end
|
||||
|
||||
def record_metrics
|
||||
metrics = self.metrics || create_metrics
|
||||
metrics.record!
|
||||
end
|
||||
|
||||
##
|
||||
# Override in issuable specialization
|
||||
#
|
||||
|
|
@ -347,6 +342,10 @@ module Issuable
|
|||
false
|
||||
end
|
||||
|
||||
def ensure_metrics
|
||||
self.metrics || create_metrics
|
||||
end
|
||||
|
||||
##
|
||||
# Overriden in MergeRequest
|
||||
#
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ module Storage
|
|||
# So we basically we mute exceptions in next actions
|
||||
begin
|
||||
send_update_instructions
|
||||
write_projects_repository_config
|
||||
|
||||
true
|
||||
rescue
|
||||
# Returning false does not rollback after_* transaction but gives
|
||||
|
|
|
|||
|
|
@ -23,8 +23,13 @@ class DiffDiscussion < Discussion
|
|||
def merge_request_version_params
|
||||
return unless for_merge_request?
|
||||
|
||||
version_params = get_params
|
||||
|
||||
return version_params unless on_merge_request_commit? && commit_id
|
||||
|
||||
version_params ||= {}
|
||||
version_params.tap do |params|
|
||||
params[:commit_id] = commit_id if on_merge_request_commit?
|
||||
params[:commit_id] = commit_id
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -37,7 +42,7 @@ class DiffDiscussion < Discussion
|
|||
|
||||
private
|
||||
|
||||
def version_params
|
||||
def get_params
|
||||
return {} if active?
|
||||
|
||||
noteable.version_params_for(position.diff_refs)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,18 @@ class Event < ActiveRecord::Base
|
|||
|
||||
belongs_to :author, class_name: "User"
|
||||
belongs_to :project
|
||||
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
|
||||
belongs_to :target, -> {
|
||||
# If the association for "target" defines an "author" association we want to
|
||||
# eager-load this so Banzai & friends don't end up performing N+1 queries to
|
||||
# get the authors of notes, issues, etc.
|
||||
if reflections['events'].active_record.reflect_on_association(:author)
|
||||
includes(:author)
|
||||
else
|
||||
self
|
||||
end
|
||||
}, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||
|
||||
has_one :push_event_payload
|
||||
|
||||
# Callbacks
|
||||
|
|
|
|||
|
|
@ -276,6 +276,11 @@ class Issue < ActiveRecord::Base
|
|||
|
||||
private
|
||||
|
||||
def ensure_metrics
|
||||
super
|
||||
metrics.record!
|
||||
end
|
||||
|
||||
# Returns `true` if the given User can read the current Issue.
|
||||
#
|
||||
# This method duplicates the same check of issue_policy.rb
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
class MergeRequest::Metrics < ActiveRecord::Base
|
||||
belongs_to :merge_request
|
||||
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id
|
||||
|
||||
def record!
|
||||
if merge_request.merged? && self.merged_at.blank?
|
||||
self.merged_at = Time.now
|
||||
end
|
||||
|
||||
self.save
|
||||
end
|
||||
belongs_to :latest_closed_by, class_name: 'User'
|
||||
belongs_to :merged_by, class_name: 'User'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -268,4 +268,11 @@ class Namespace < ActiveRecord::Base
|
|||
def namespace_previously_created_with_same_path?
|
||||
RedirectRoute.permanent.exists?(path: path)
|
||||
end
|
||||
|
||||
def write_projects_repository_config
|
||||
all_projects.find_each do |project|
|
||||
project.expires_full_path_cache # we need to clear cache to validate renames correctly
|
||||
project.write_repository_config
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ class Project < ActiveRecord::Base
|
|||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
delegate :members, to: :team, prefix: true
|
||||
delegate :add_user, :add_users, to: :team
|
||||
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
|
||||
delegate :add_guest, :add_reporter, :add_developer, :add_master, :add_role, to: :team
|
||||
|
||||
# Validations
|
||||
validates :creator, presence: true, on: :create
|
||||
|
|
@ -639,7 +639,7 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def import?
|
||||
external_import? || forked? || gitlab_project_import?
|
||||
external_import? || forked? || gitlab_project_import? || bare_repository_import?
|
||||
end
|
||||
|
||||
def no_import?
|
||||
|
|
@ -679,6 +679,10 @@ class Project < ActiveRecord::Base
|
|||
Gitlab::UrlSanitizer.new(import_url).masked_url
|
||||
end
|
||||
|
||||
def bare_repository_import?
|
||||
import_type == 'bare_repository'
|
||||
end
|
||||
|
||||
def gitlab_project_import?
|
||||
import_type == 'gitlab_project'
|
||||
end
|
||||
|
|
@ -1416,6 +1420,8 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def after_rename_repo
|
||||
write_repository_config
|
||||
|
||||
path_before_change = previous_changes['path'].first
|
||||
|
||||
# We need to check if project had been rolled out to move resource to hashed storage or not and decide
|
||||
|
|
@ -1428,6 +1434,16 @@ class Project < ActiveRecord::Base
|
|||
Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path)
|
||||
end
|
||||
|
||||
def write_repository_config(gl_full_path: full_path)
|
||||
# We'd need to keep track of project full path otherwise directory tree
|
||||
# created with hashed storage enabled cannot be usefully imported using
|
||||
# the import rake task.
|
||||
repo.config['gitlab.fullpath'] = gl_full_path
|
||||
rescue Gitlab::Git::Repository::NoRepository => e
|
||||
Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
|
||||
nil
|
||||
end
|
||||
|
||||
def rename_repo_notify!
|
||||
send_move_instructions(full_path_was)
|
||||
expires_full_path_cache
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class KubernetesService < DeploymentService
|
|||
|
||||
before_validation :enforce_namespace_to_lower_case
|
||||
|
||||
validate :deprecation_validation, unless: :template?
|
||||
validates :namespace,
|
||||
allow_blank: true,
|
||||
length: 1..63,
|
||||
|
|
@ -145,6 +146,17 @@ class KubernetesService < DeploymentService
|
|||
@kubeclient ||= build_kubeclient!
|
||||
end
|
||||
|
||||
def deprecated?
|
||||
!active
|
||||
end
|
||||
|
||||
def deprecation_message
|
||||
content = <<-MESSAGE.strip_heredoc
|
||||
Kubernetes service integration has been deprecated. #{deprecated_message_content} your clusters using the new <a href=\'#{Gitlab::Routing.url_helpers.project_clusters_path(project)}'/>Clusters</a> page
|
||||
MESSAGE
|
||||
content.html_safe
|
||||
end
|
||||
|
||||
TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze
|
||||
|
||||
private
|
||||
|
|
@ -226,4 +238,20 @@ class KubernetesService < DeploymentService
|
|||
def enforce_namespace_to_lower_case
|
||||
self.namespace = self.namespace&.downcase
|
||||
end
|
||||
|
||||
def deprecation_validation
|
||||
return if active_changed?(from: true, to: false)
|
||||
|
||||
if deprecated?
|
||||
errors[:base] << deprecation_message
|
||||
end
|
||||
end
|
||||
|
||||
def deprecated_message_content
|
||||
if active?
|
||||
"Your cluster information on this page is still editable, but you are advised to disable and reconfigure"
|
||||
else
|
||||
"Fields on this page are now uneditable, you can configure"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,36 +7,24 @@ class ProjectTeam
|
|||
@project = project
|
||||
end
|
||||
|
||||
# Shortcut to add users
|
||||
#
|
||||
# Use:
|
||||
# @team << [@user, :master]
|
||||
# @team << [@users, :master]
|
||||
#
|
||||
def <<(args)
|
||||
users, access, current_user = *args
|
||||
|
||||
if users.respond_to?(:each)
|
||||
add_users(users, access, current_user: current_user)
|
||||
else
|
||||
add_user(users, access, current_user: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def add_guest(user, current_user: nil)
|
||||
self << [user, :guest, current_user]
|
||||
add_user(user, :guest, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_reporter(user, current_user: nil)
|
||||
self << [user, :reporter, current_user]
|
||||
add_user(user, :reporter, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_developer(user, current_user: nil)
|
||||
self << [user, :developer, current_user]
|
||||
add_user(user, :developer, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_master(user, current_user: nil)
|
||||
self << [user, :master, current_user]
|
||||
add_user(user, :master, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_role(user, role, current_user: nil)
|
||||
send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
def find_member(user_id)
|
||||
|
|
|
|||
|
|
@ -1010,10 +1010,6 @@ class Repository
|
|||
raw_repository.fetch_source_branch!(source_repository.raw_repository, source_branch, local_ref)
|
||||
end
|
||||
|
||||
def remote_exists?(name)
|
||||
raw_repository.remote_exists?(name)
|
||||
end
|
||||
|
||||
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
|
||||
raw_repository.compare_source_branch(target_branch_name, source_repository.raw_repository, source_branch_name, straight: straight)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -263,6 +263,14 @@ class Service < ActiveRecord::Base
|
|||
service
|
||||
end
|
||||
|
||||
def deprecated?
|
||||
false
|
||||
end
|
||||
|
||||
def deprecation_message
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_project_has_external_issue_tracker
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ class User < ActiveRecord::Base
|
|||
has_one :user_synced_attributes_metadata, autosave: true
|
||||
|
||||
# Groups
|
||||
has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :members
|
||||
has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember'
|
||||
has_many :groups, through: :group_members
|
||||
has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
|
||||
has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
|
||||
|
|
@ -103,7 +103,7 @@ class User < ActiveRecord::Base
|
|||
# Projects
|
||||
has_many :groups_projects, through: :groups, source: :projects
|
||||
has_many :personal_projects, through: :namespace, source: :projects
|
||||
has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :project_members, -> { where(requested_at: nil) }
|
||||
has_many :projects, through: :project_members
|
||||
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
|
||||
has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
|
@ -794,10 +794,7 @@ class User < ActiveRecord::Base
|
|||
# `User.select(:id)` raises
|
||||
# `ActiveModel::MissingAttributeError: missing attribute: projects_limit`
|
||||
# without this safeguard!
|
||||
return unless has_attribute?(:projects_limit)
|
||||
|
||||
connection_default_value_defined = new_record? && !projects_limit_changed?
|
||||
return unless projects_limit.nil? || connection_default_value_defined
|
||||
return unless has_attribute?(:projects_limit) && projects_limit.nil?
|
||||
|
||||
self.projects_limit = current_application_settings.default_projects_limit
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
class EventEntity < Grape::Entity
|
||||
expose :author, using: UserEntity
|
||||
expose :updated_at
|
||||
end
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
class MergeRequestMetricsEntity < Grape::Entity
|
||||
expose :latest_closed_at, as: :closed_at
|
||||
expose :merged_at
|
||||
expose :latest_closed_by, as: :closed_by, using: UserEntity
|
||||
expose :merged_by, using: UserEntity
|
||||
end
|
||||
|
|
@ -17,9 +17,11 @@ class MergeRequestWidgetEntity < IssuableEntity
|
|||
merge_request.project.merge_requests_ff_only_enabled
|
||||
end
|
||||
|
||||
# Events
|
||||
expose :merge_event, using: EventEntity
|
||||
expose :closed_event, using: EventEntity
|
||||
expose :metrics do |merge_request|
|
||||
metrics = build_metrics(merge_request)
|
||||
|
||||
MergeRequestMetricsEntity.new(metrics).as_json
|
||||
end
|
||||
|
||||
# User entities
|
||||
expose :merge_user, using: UserEntity
|
||||
|
|
@ -178,4 +180,27 @@ class MergeRequestWidgetEntity < IssuableEntity
|
|||
@presenters ||= {}
|
||||
@presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user)
|
||||
end
|
||||
|
||||
# Once SchedulePopulateMergeRequestMetricsWithEventsData fully runs,
|
||||
# we can remove this method and just serialize MergeRequest#metrics
|
||||
# instead. See https://gitlab.com/gitlab-org/gitlab-ce/issues/41587
|
||||
def build_metrics(merge_request)
|
||||
# There's no need to query and serialize metrics data for merge requests that are not
|
||||
# merged or closed.
|
||||
return unless merge_request.merged? || merge_request.closed?
|
||||
return merge_request.metrics if merge_request.merged? && merge_request.metrics&.merged_by_id
|
||||
return merge_request.metrics if merge_request.closed? && merge_request.metrics&.latest_closed_by_id
|
||||
|
||||
build_metrics_from_events(merge_request)
|
||||
end
|
||||
|
||||
def build_metrics_from_events(merge_request)
|
||||
closed_event = merge_request.closed_event
|
||||
merge_event = merge_request.merge_event
|
||||
|
||||
MergeRequest::Metrics.new(latest_closed_at: closed_event&.updated_at,
|
||||
latest_closed_by: closed_event&.author,
|
||||
merged_at: merge_event&.updated_at,
|
||||
merged_by: merge_event&.author)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -103,6 +103,6 @@ class EventCreateService
|
|||
author_id: current_user.id
|
||||
)
|
||||
|
||||
Event.create(attributes)
|
||||
Event.create!(attributes)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
class MergeRequestMetricsService
|
||||
delegate :update!, to: :@merge_request_metrics
|
||||
|
||||
def initialize(merge_request_metrics)
|
||||
@merge_request_metrics = merge_request_metrics
|
||||
end
|
||||
|
||||
def merge(event)
|
||||
update!(merged_by_id: event.author_id, merged_at: event.created_at)
|
||||
end
|
||||
|
||||
def close(event)
|
||||
update!(latest_closed_by_id: event.author_id, latest_closed_at: event.created_at)
|
||||
end
|
||||
|
||||
def reopen
|
||||
update!(latest_closed_by_id: nil, latest_closed_at: nil)
|
||||
end
|
||||
end
|
||||
|
|
@ -24,6 +24,10 @@ module MergeRequests
|
|||
|
||||
private
|
||||
|
||||
def merge_request_metrics_service(merge_request)
|
||||
MergeRequestMetricsService.new(merge_request.metrics)
|
||||
end
|
||||
|
||||
def create_assignee_note(merge_request)
|
||||
SystemNoteService.change_assignee(
|
||||
merge_request, merge_request.project, current_user, merge_request.assignee)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ module MergeRequests
|
|||
merge_request.allow_broken = true
|
||||
|
||||
if merge_request.close
|
||||
event_service.close_mr(merge_request, current_user)
|
||||
create_event(merge_request)
|
||||
create_note(merge_request)
|
||||
notification_service.close_mr(merge_request, current_user)
|
||||
todo_service.close_merge_request(merge_request, current_user)
|
||||
|
|
@ -19,5 +19,16 @@ module MergeRequests
|
|||
|
||||
merge_request
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_event(merge_request)
|
||||
# Making sure MergeRequest::Metrics updates are in sync with
|
||||
# Event creation.
|
||||
Event.transaction do
|
||||
close_event = event_service.close_mr(merge_request, current_user)
|
||||
merge_request_metrics_service(merge_request).close(close_event)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module MergeRequests
|
|||
# when there are no conflict files.
|
||||
conflicts.files.each(&:lines)
|
||||
@conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
|
||||
rescue Rugged::OdbError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing
|
||||
rescue Gitlab::Git::CommandError, Gitlab::Git::Conflict::Parser::UnresolvableError, Gitlab::Git::Conflict::Resolver::ConflictSideMissing
|
||||
@conflicts_can_be_resolved_in_ui = false
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue