Commit 3c1f9683 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into list-multiple-clusters

* master: (158 commits)
  Improve output for extra queries in specs
  Fixed new group milestone breadcrumb
  Try to find the merge-base against the canonical master
  Add FetchSourceBranch Gitaly call
  Backport QA code that belongs to CE from EE Geo
  Add QUERY_RECORDER_DEBUG environment variable to improve performance debugging
  Add support of Mermaid
  Update VERSION to 10.3.0-pre
  Update CHANGELOG.md for 10.2.0
  default fill color for SVGs
  Fix reply quote keyboard shortcut on MRs
  ignore hashed repos (for now) when using `rake gitlab:cleanup:repos`
  Use Redis cache for branch existence checks
  Update CONTRIBUTING.md: Link definition of done to criteria
  Use `make install` for Gitaly setups in non-test environments
  FileUploader should check for hashed_storage?(:attachments) to use disk_path
  Set the default gitlab-shell timeout to 3 hours
  Update composite pipelines index to include "id"
  Use arrays in Pipeline#latest_builds_with_artifacts
  Fix blank states using old css
  ...
parents afcfe182 0c972f56
......@@ -193,7 +193,7 @@ review-docs-deploy:
name: review-docs/$CI_COMMIT_REF_NAME
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://preview-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup
script:
- ./trigger-build-docs deploy
......@@ -256,7 +256,7 @@ flaky-examples-check:
USE_BUNDLE_INSTALL: "false"
NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json
stage: post-test
allow_failure: yes
allow_failure: true
retry: 0
only:
- branches
......@@ -416,11 +416,10 @@ ee_compat_check:
- /^[\d-]+-stable(-ee)?/
- branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee
allow_failure: no
retry: 0
artifacts:
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
when: on_failure
when: always
expire_in: 10d
paths:
- ee_compat_check/patches/*.patch
......@@ -475,7 +474,7 @@ migration:path-mysql:
<<: *pull-cache
stage: test
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:rollback STEP=119
- bundle exec rake db:migrate
db:rollback-pg:
......@@ -578,7 +577,7 @@ codequality:
script:
- cp .rubocop.yml .rubocop.yml.bak
- grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
- mv .rubocop.yml.bak .rubocop.yml
artifacts:
......
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "feature proposal" label:
For the Community Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
For the Enterprise Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=feature+proposal
and verify the issue you're about to submit isn't a duplicate.
Please remove this notice if you're confident your issue isn't a duplicate.
------
### Description
(Include problem, use cases, benefits, and/or goals)
......@@ -25,26 +6,4 @@ Please remove this notice if you're confident your issue isn't a duplicate.
### Links / references
### Documentation blurb
#### Overview
What is it?
Why should someone use this feature?
What is the underlying (business) problem?
How do you use this feature?
#### Use cases
Who is this for? Provide one or more use cases.
### Feature checklist
Make sure these are completed before closing the issue,
with a link to the relevant commit.
- [ ] [Feature assurance](https://about.gitlab.com/handbook/product/#feature-assurance)
- [ ] Documentation
- [ ] Added to [features.yml](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
/label ~"feature proposal"
\ No newline at end of file
/label ~"feature proposal"
This diff is collapsed.
......@@ -543,6 +543,7 @@ When having your code reviewed and when reviewing merge requests please take the
etc.), they should conform to our [Licensing guidelines][license-finder-doc].
See the instructions in that document for help if your MR fails the
"license-finder" test with a "Dependencies that need approval" error.
1. The merge request meets the [definition of done](#definition-of-done).
## Definition of done
......
......@@ -263,6 +263,8 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.2.0'
gem 'gettext', '~> 3.2.2', require: false, group: :development
gem 'batch-loader'
# Perf bar
gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2'
......@@ -343,7 +345,7 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'license_finder', '~> 2.1.0', require: false
gem 'license_finder', '~> 3.1', require: false
gem 'knapsack', '~> 1.11.0'
gem 'activerecord_sane_schema_dumper', '0.2'
......@@ -398,7 +400,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.52.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.54.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -73,6 +73,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
base32 (0.3.2)
batch-loader (1.1.1)
bcrypt (3.1.11)
bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0)
......@@ -83,6 +84,7 @@ GEM
bindata (2.4.1)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
......@@ -274,7 +276,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.52.0)
gitaly-proto (0.54.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -354,10 +356,10 @@ GEM
rake
grape_logging (1.7.0)
grape
grpc (1.6.6)
grpc (1.7.2)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
googleauth (~> 0.5.1)
googleauth (>= 0.5.1, < 0.7)
haml (4.0.7)
tilt
haml_lint (0.26.0)
......@@ -451,11 +453,13 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
license_finder (2.1.0)
license_finder (3.1.1)
bundler
httparty
rubyzip
thor
toml (= 0.1.2)
with_env (> 1.0)
xml-simple
licensee (8.7.0)
rugged (~> 0.24)
......@@ -571,6 +575,8 @@ GEM
activerecord (>= 4.0, < 5.2)
parser (2.4.0.0)
ast (~> 2.2)
parslet (1.5.0)
blankslate (~> 2.0)
path_expander (1.0.1)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
......@@ -898,6 +904,8 @@ GEM
tilt (2.0.6)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
toml (0.1.2)
parslet (~> 1.5.0)
toml-rb (0.3.15)
citrus (~> 3.0, > 3.0)
truncato (0.7.10)
......@@ -952,6 +960,7 @@ GEM
builder
expression_parser
rinku
with_env (1.1.0)
xml-simple (1.1.5)
xpath (2.1.0)
nokogiri (~> 1.3)
......@@ -974,6 +983,7 @@ DEPENDENCIES
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
batch-loader
bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
......@@ -1026,7 +1036,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.52.0)
gitaly-proto (~> 0.54.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
......@@ -1058,7 +1068,7 @@ DEPENDENCIES
knapsack (~> 1.11.0)
kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0)
license_finder (~> 3.1)
licensee (~> 8.7.0)
lograge (~> 0.5)
loofah (~> 2.0.3)
......
10.2.0-pre
10.3.0-pre
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
/* global EditBlob */
/* global NewCommitForm */
import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone';
......
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global BoardService */
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';
import eventHub from './eventhub';
import sidebarEventHub from '../sidebar/event_hub';
import './models/issue';
import './models/label';
import './models/list';
......@@ -14,7 +15,7 @@ import './models/milestone';
import './models/assignee';
import './stores/boards_store';
import './stores/modal_store';
import './services/board_service';
import BoardService from './services/board_service';
import './mixins/modal_mixins';
import './mixins/sortable_default_options';
import './filters/due_date_filters';
......@@ -77,11 +78,16 @@ $(() => {
});
Store.rootPath = this.boardsEndpoint;
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens);
eventHub.$on('newDetailIssue', this.updateDetailIssue);
eventHub.$on('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$on('toggleSubscription', this.toggleSubscription);
},
beforeDestroy() {
eventHub.$off('updateTokens', this.updateTokens);
eventHub.$off('newDetailIssue', this.updateDetailIssue);
eventHub.$off('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
},
mounted () {
this.filterManager = new FilteredSearchBoards(Store.filter, true);
......@@ -112,6 +118,46 @@ $(() => {
methods: {
updateTokens() {
this.filterManager.updateTokens();
},
updateDetailIssue(newIssue) {
const sidebarInfoEndpoint = newIssue.sidebarInfoEndpoint;
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
newIssue.setFetchingState('subscriptions', true);
BoardService.getIssueInfo(sidebarInfoEndpoint)
.then(res => res.json())
.then((data) => {
newIssue.setFetchingState('subscriptions', false);
newIssue.updateData({
subscribed: data.subscribed,
});
})
.catch(() => {
newIssue.setFetchingState('subscriptions', false);
Flash(__('An error occurred while fetching sidebar data'));
});
}
Store.detail.issue = newIssue;
},
clearDetailIssue() {
Store.detail.issue = {};
},
toggleSubscription(id) {
const issue = Store.detail.issue;
if (issue.id === id && issue.toggleSubscriptionEndpoint) {
issue.setFetchingState('subscriptions', true);
BoardService.toggleIssueSubscription(issue.toggleSubscriptionEndpoint)
.then(() => {
issue.setFetchingState('subscriptions', false);
issue.updateData({
subscribed: !issue.subscribed,
});
})
.catch(() => {
issue.setFetchingState('subscriptions', false);
Flash(__('An error occurred when toggling the notification subscription'));
});
}
}
},
});
......
<script>
import './issue_card_inner';
import eventHub from '../eventhub';
const Store = gl.issueBoards.BoardsStore;
export default {
name: 'BoardsIssueCard',
template: `
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="showIssue($event)">
<issue-card-inner
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
:update-filters="true" />
</li>
`,
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
......@@ -56,12 +42,30 @@ export default {
this.showDetail = false;
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
Store.detail.issue = {};
eventHub.$emit('clearDetailIssue');
} else {
Store.detail.issue = this.issue;
eventHub.$emit('newDetailIssue', this.issue);
Store.detail.list = this.list;
}
}
},
},
};
</script>
<template>
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="showIssue($event)">
<issue-card-inner
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
:update-filters="true" />
</li>
</template>
/* global Sortable */
import boardNewIssue from './board_new_issue';
import boardCard from './board_card';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
......
......@@ -5,12 +5,13 @@
import Vue from 'vue';
import Flash from '../../flash';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees';
import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
import assignees from '../../sidebar/components/assignees/assignees';
import DueDateSelectors from '../../due_date_select';
import './sidebar/remove_issue';
import IssuableContext from '../../issuable_context';
import LabelsSelect from '../../labels_select';
import subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
const Store = gl.issueBoards.BoardsStore;
......@@ -117,11 +118,11 @@ gl.issueBoards.BoardSidebar = Vue.extend({
new DueDateSelectors();
new LabelsSelect();
new Sidebar();
gl.Subscription.bindAll('.subscription');
},
components: {
assigneeTitle,
assignees,
removeBtn: gl.issueBoards.RemoveIssueBtn,
'assignee-title': AssigneeTitle,
assignees: Assignees,
subscriptions,
},
});
......@@ -17,6 +17,11 @@ class ListIssue {
this.assignees = [];
this.selected = false;
this.position = obj.relative_position || Infinity;
this.isFetching = {
subscriptions: true,
};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
if (obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
......@@ -73,6 +78,14 @@ class ListIssue {
return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id));
}
updateData(newData) {
Object.assign(this, newData);
}
setFetchingState(key, value) {
this.isFetching[key] = value;
}
update (url) {
const data = {
issue: {
......
......@@ -2,7 +2,7 @@
import Vue from 'vue';
class BoardService {
export default class BoardService {
constructor ({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) {
this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, {
issues: {
......@@ -88,6 +88,14 @@ class BoardService {
return this.issues.bulkUpdate(data);
}
static getIssueInfo(endpoint) {
return Vue.http.get(endpoint);
}
static toggleIssueSubscription(endpoint) {
return Vue.http.post(endpoint);
}
}
window.BoardService = BoardService;
import axios from 'axios';
import setAxiosCsrfToken from '../../lib/utils/axios_utils';
import axios from '../../lib/utils/axios_utils';
export default class ClusterService {
constructor(options = {}) {
setAxiosCsrfToken();
this.options = options;
this.appInstallEndpointMap = {
helm: this.options.installHelmEndpoint,
......@@ -18,7 +15,6 @@ export default class ClusterService {
}
installApplication(appId) {
const endpoint = this.appInstallEndpointMap[appId];
return axios.post(endpoint);
return axios.post(this.appInstallEndpointMap[appId]);
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
import { s__ } from './locale';
/* global ProjectSelect */
import projectSelect from './project_select';
import IssuableIndex from './issuable_index';
/* global Milestone */
import Milestone from './milestone';
import IssuableForm from './issuable_form';
import LabelsSelect from './labels_select';
/* global MilestoneSelect */
/* global NewBranchForm */
import NewBranchForm from './new_branch_form';
/* global NotificationsForm */
/* global NotificationsDropdown */
import groupAvatar from './group_avatar';
......@@ -18,16 +18,14 @@ import groupsSelect from './groups_select';
/* global Search */
/* global Admin */
import NamespaceSelect from './namespace_select';
/* global NewCommitForm */
/* global NewBranchForm */
import NewCommitForm from './new_commit_form';
import Project from './project';
import projectAvatar from './project_avatar';
/* global MergeRequest */
/* global Compare */
/* global CompareAutocomplete */
/* global ProjectFindFile */
/* global ProjectNew */
/* global ProjectShow */
import ProjectNew from './project_new';
import projectImport from './project_import';
import Labels from './labels';
import LabelManager from './label_manager';
......@@ -91,6 +89,8 @@ import Members from './members';
import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select';
import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription';
import ProjectVariables from './project_variables';
(function() {
var Dispatcher;
......@@ -187,7 +187,7 @@ import Diff from './diff';
initIssuableSidebar();
break;
case 'dashboard:milestones:index':
new ProjectSelect();
projectSelect();
break;
case 'projects:milestones:show':
case 'groups:milestones:show':
......@@ -197,7 +197,7 @@ import Diff from './diff';
break;
case 'dashboard:issues':
case 'dashboard:merge_requests':
new ProjectSelect();
projectSelect();
initLegacyFilters();
break;
case 'groups:issues':
......@@ -206,7 +206,7 @@ import Diff from './diff';
const filteredSearchManager = new gl.FilteredSearchManager(page === 'groups:issues' ? 'issues' : 'merge_requests');
filteredSearchManager.setup();
}
new ProjectSelect();
projectSelect();
break;
case 'dashboard:todos:index':
new Todos();
......@@ -317,7 +317,6 @@ import Diff from './diff';
break;
case 'projects:merge_requests:show':
new Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode();
initIssuableSidebar();
......@@ -327,6 +326,8 @@ import Diff from './diff';
window.mergeRequest = new MergeRequest({
action: mrShowNode.dataset.mrAction,
});
shortcut_handler = new ShortcutsIssuable(true);
break;
case 'dashboard:activity':
new gl.Activities();
......@@ -339,7 +340,8 @@ import Diff from './diff';
container: '.js-commit-pipeline-graph',
}).bindEvents();
initNotes();
initChangesDropdown();
const stickyBarPaddingTop = 16;
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:commit:pipelines':
......@@ -484,7 +486,7 @@ import Diff from './diff';
if ($el.find('.dropdown-group-label').length) {
new GroupLabelSubscription($el);
} else {
new gl.ProjectLabelSubscription($el);
new ProjectLabelSubscription($el);
}
});
break;
......@@ -520,7 +522,7 @@ import Diff from './diff';
// Initialize expandable settings panels
initSettingsPanels();
case 'groups:settings:ci_cd:show':
new gl.ProjectVariables();
new ProjectVariables();
break;
case 'ci:lints:create':
case 'ci:lints:show':
......@@ -623,7 +625,6 @@ import Diff from './diff';
case 'show':
new Star();
new ProjectNew();
new ProjectShow();
new NotificationsDropdown();
break;
case 'wikis':
......
......@@ -227,25 +227,27 @@ export default {
/>
<div
class="blank-state blank-state-no-icon"
class="blank-state-row"
v-if="!isLoading && state.environments.length === 0">
<h2 class="blank-state-title js-blank-state-title">
You don't have any environments right now.
</h2>
<p class="blank-state-text">
Environments are places where code gets deployed, such as staging or production.
<br />
<a :href="helpPagePath">
Read more about environments
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">
You don't have any environments right now.
</h2>
<p class="blank-state-text">
Environments are places where code gets deployed, such as staging or production.
<br />
<a :href="helpPagePath">
Read more about environments
</a>
</p>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create js-new-environment-button">
New environment
</a>
</p>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create js-new-environment-button">
New environment
</a>
</div>
</div>
<div
......
......@@ -14,7 +14,6 @@ export default () => {
});
new LabelsSelect();
new IssuableContext(sidebarOptions.currentUser);
gl.Subscription.bindAll('.subscription');
new DueDateSelectors();
window.sidebar = new Sidebar();
};
/* eslint-disable no-new */
import LabelsSelect from './labels_select';
/* global MilestoneSelect */
/* global SubscriptionSelect */
import subscriptionSelect from './subscription_select';
import UsersSelect from './users_select';
import issueStatusSelect from './issue_status_select';
......@@ -11,5 +10,5 @@ export default () => {
new LabelsSelect();
new MilestoneSelect();
issueStatusSelect();
new SubscriptionSelect();
subscriptionSelect();
};
/* eslint-disable class-methods-use-this, no-new */
/* global MilestoneSelect */
/* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import './milestone_select';
import issueStatusSelect from './issue_status_select';
import './subscription_select';
import subscriptionSelect from './subscription_select';
import LabelsSelect from './labels_select';
const HIDDEN_CLASS = 'hidden';
......@@ -48,7 +47,7 @@ export default class IssuableBulkUpdateSidebar {
new LabelsSelect();
new MilestoneSelect();
issueStatusSelect();
new SubscriptionSelect();
subscriptionSelect();
}
setupBulkUpdateActions() {
......
......@@ -102,6 +102,11 @@ export default {
required: false,
default: 'issue',
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
},
data() {
const store = new Store({
......@@ -234,6 +239,7 @@ export default {
:project-path="projectPath"
:project-namespace="projectNamespace"
:show-delete-button="showDeleteButton"
:can-attach-file="canAttachFile"
/>
<div v-else>
<title-component
......
......@@ -17,6 +17,11 @@
type: String,
required: true,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
},
components: {
markdownField,
......@@ -36,7 +41,8 @@
</label>
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath">
:markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile">
<textarea
id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area"
......
......@@ -41,6 +41,11 @@
required: false,
default: true,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
},
components: {
lockedWarning,
......@@ -83,7 +88,8 @@
<description-field
:form-state="formState"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" />
:markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile" />
<edit-actions
:form-state="formState"
:can-destroy="canDestroy"
......
......@@ -29,8 +29,8 @@ export default class JobMediator {
this.poll = new Poll({
resource: this.service,
method: 'getJob',
successCallback: this.successCallback.bind(this),
errorCallback: this.errorCallback.bind(this),
successCallback: response => this.successCallback(response),
errorCallback: () => this.errorCallback(),
});
if (!Visibility.hidden()) {
......@@ -57,7 +57,7 @@ export default class JobMediator {
successCallback(response) {
this.state.isLoading = false;
return response.json().then(data => this.store.storeJob(data));
return this.store.storeJob(response.data);
}
errorCallback() {
......
import Vue from 'vue';
import VueResource from 'vue-resource';
Vue.use(VueResource);
import axios from '../../lib/utils/axios_utils';
export default class JobService {
constructor(endpoint) {
this.job = Vue.resource(endpoint);
this.job = endpoint;
}
getJob() {
return this.job.get();
return axios.get(this.job);
}
}
import axios from 'axios';
import csrf from './csrf';
export default function setAxiosCsrfToken() {
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
}
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
// Maintain a global counter for active requests
// see: spec/support/wait_for_requests.rb
axios.interceptors.request.use((config) => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
return config;
});
// Remove the global counter
axios.interceptors.response.use((config) => {
window.activeVueResources -= 1;
return config;
});
export default axios;
......@@ -3,7 +3,9 @@ import { normalizeHeaders } from './common_utils';
/**
* Polling utility for handling realtime updates.
* Service for vue resouce and method need to be provided as props
* Requirements: Promise based HTTP client
*
* Service for promise based http client and method need to be provided as props
*
* @example
* new Poll({
......
......@@ -29,7 +29,6 @@ import './commit/image_file';
// lib/utils
import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility';
import './lib/utils/pretty_time';
import './lib/utils/url_utility';
// behaviors
......@@ -59,11 +58,7 @@ import './line_highlighter';
import initLogoAnimation from './logo';
import './merge_request';
import './merge_request_tabs';
import './milestone';
import './milestone_select';
import './namespace_select';
import './new_branch_form';
import './new_commit_form';
import './notes';
import './notifications_dropdown';
import './notifications_form';
......@@ -71,22 +66,15 @@ import './pager';
import './preview_markdown';
import './project_find_file';
import './project_import';
import './project_label_subscription';
import './project_new';
import './project_select';
import './project_show';
import './project_variables';
import './projects_dropdown';
import './projects_list';
import './syntax_highlight';
import './render_math';
import './render_mermaid';
import './render_gfm';
import './right_sidebar';
import './search';
import './search_autocomplete';
import './smart_interval';
import './subscription';
import './subscription_select';
import initBreadcrumbs from './breadcrumb';
import './dispatcher';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */
/* global Sortable */
import Flash from './flash';
(function() {
this.Milestone = (function() {
function Milestone() {
this.bindTabsSwitching();
export default class Milestone {
constructor() {
this.bindTabsSwitching();
// Load merge request tab if it is active
// merge request tab is active based on different conditions in the backend
this.loadTab($('.js-milestone-tabs .active a'));
// Load merge request tab if it is active
// merge request tab is active based on different conditions in the backend
this.loadTab($('.js-milestone-tabs .active a'));
this.loadInitialTab();
}
this.loadInitialTab();
}
bindTabsSwitching() {
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
const $target = $(e.target);
Milestone.prototype.bindTabsSwitching = function() {
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
const $target = $(e.target);
location.hash = $target.attr('href');
this.loadTab($target);
});
}
// eslint-disable-next-line class-methods-use-this
loadInitialTab() {
const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
location.hash = $target.attr('href');
this.loadTab($target);
if ($target.length) {
$target.tab('show');
}
}
// eslint-disable-next-line class-methods-use-this
loadTab($target) {
const endpoint = $target.data('endpoint');
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
$.ajax({
url: endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error loading milestone tab'))
.done((data) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
});
};
Milestone.prototype.loadInitialTab = function() {
const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
if ($target.length) {
$target.tab('show');
}
};
Milestone.prototype.loadTab = function($target) {
const endpoint = $target.data('endpoint');
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
$.ajax({
url: endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error loading milestone tab'))
.done((data) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
});
}
};
return Milestone;
})();
}).call(window);
}
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
import RefSelectDropdown from '~/ref_select_dropdown';
import RefSelectDropdown from './ref_select_dropdown';
(function() {
this.NewBranchForm = (function() {
function NewBranchForm(form, availableRefs) {
this.validate = this.validate.bind(this);
this.branchNameError = form.find('.js-branch-name-error');
this.name = form.find('.js-branch-name');
this.ref = form.find('#ref');
new RefSelectDropdown($('.js-branch-select'), availableRefs); // eslint-disable-line no-new
this.setupRestrictions();
this.addBinding();
this.init();
export default class NewBranchForm {
constructor(form, availableRefs) {
this.validate = this.validate.bind(this);
this.branchNameError = form.find('.js-branch-name-error');
this.name = form.find('.js-branch-name');
this.ref = form.find('#ref');
new RefSelectDropdown($('.js-branch-select'), availableRefs); // eslint-disable-line no-new
this.setupRestrictions();
this.addBinding();
this.init();
}
addBinding() {
return this.name.on('blur', this.validate);
}
init() {
if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
}
}
NewBranchForm.prototype.addBinding = function() {
return this.name.on('blur', this.validate);
setupRestrictions() {
var endsWith, invalid, single, startsWith;
startsWith = {
pattern: /^(\/|\.)/g,
prefix: "can't start with",
conjunction: "or"
};
NewBranchForm.prototype.init = function() {
if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
}
endsWith = {
pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in",
conjunction: "or"
};
NewBranchForm.prototype.setupRestrictions = function() {
var endsWith, invalid, single, startsWith;
startsWith = {
pattern: /^(\/|\.)/g,
prefix: "can't start with",
conjunction: "or"
};
endsWith = {
pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in",
conjunction: "or"
};
invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
prefix: "can't contain",
conjunction: ", "
};
single = {
pattern: /^@+$/g,
prefix: "can't be",
conjunction: "or"
};
return this.restrictions = [startsWith, invalid, endsWith, single];
invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
prefix: "can't contain",
conjunction: ", "
};
single = {
pattern: /^@+$/g,
prefix: "can't be",
conjunction: "or"
};
return this.restrictions = [startsWith, invalid, endsWith, single];
}
NewBranchForm.prototype.validate = function() {
var errorMessage, errors, formatter, unique, validator;
const indexOf = [].indexOf;
validate() {
var errorMessage, errors, formatter, unique, validator;
const indexOf = [].indexOf;
this.branchNameError.empty();
unique = function(values, value) {
if (indexOf.call(values, value) === -1) {
values.push(value);
}
return values;
};
formatter = function(values, restriction) {
var formatted;
formatted = values.map(function(value) {
switch (false) {
case !/\s/.test(value):
return 'spaces';
case !/\/{2,}/g.test(value):
return 'consecutive slashes';
default:
return "'" + value + "'";
}
});
return restriction.prefix + " " + (formatted.join(restriction.conjunction));
};
validator = (function(_this) {
return function(errors, restriction) {
var matched;
matched = _this.name.val().match(restriction.pattern);
if (matched) {
return errors.concat(formatter(matched.reduce(unique, []), restriction));
} else {
return errors;
}
};
})(this);
errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
errorMessage = $("<span/>").text(errors.join(', '));
return this.branchNameError.append(errorMessage);
this.branchNameError.empty();
unique = function(values, value) {
if (indexOf.call(values, value) === -1) {
values.push(value);
}
return values;
};
return NewBranchForm;
})();
}).call(window);
formatter = function(values, restriction) {
var formatted;
formatted = values.map(function(value) {
switch (false) {
case !/\s/.test(value):
return 'spaces';
case !/\/{2,}/g.test(value):
return 'consecutive slashes';
default:
return "'" + value + "'";
}
});
return restriction.prefix + " " + (formatted.join(restriction.conjunction));
};
validator = (function(_this) {
return function(errors, restriction) {
var matched;
matched = _this.name.val().match(restriction.pattern);
if (matched) {
return errors.concat(formatter(matched.reduce(unique, []), restriction));
} else {
return errors;
}
};
})(this);
errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
errorMessage = $("<span/>").text(errors.join(', '));
return this.branchNameError.append(errorMessage);
}
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */
(function() {
this.NewCommitForm = (function() {
function NewCommitForm(form) {
this.form = form;
this.renderDestination = this.renderDestination.bind(this);
this.branchName = form.find('.js-branch-name');
this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request');
this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
this.branchName.keyup(this.renderDestination);
this.renderDestination();
}
export default class NewCommitForm {
constructor(form) {
this.form = form;
this.renderDestination = this.renderDestination.bind(this);
this.branchName = form.find('.js-branch-name');
this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request');
this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
this.branchName.keyup(this.renderDestination);
this.renderDestination();
}
NewCommitForm.prototype.renderDestination = function() {
var different;
different = this.branchName.val() !== this.originalBranch.val();
if (different) {
this.createMergeRequestContainer.show();
if (!this.wasDifferent) {
this.createMergeRequest.prop('checked', true);
}
} else {
this.createMergeRequestContainer.hide();
this.createMergeRequest.prop('checked', false);
renderDestination() {
var different;
different = this.branchName.val() !== this.originalBranch.val();
if (different) {
this.createMergeRequestContainer.show();
if (!this.wasDifferent) {
this.createMergeRequest.prop('checked', true);
}
return this.wasDifferent = different;
};
return NewCommitForm;
})();
}).call(window);
} else {
this.createMergeRequestContainer.hide();
this.createMergeRequest.prop('checked', false);
}
return this.wasDifferent = different;
}
}
<script>
import Icon from '../../vue_shared/components/icon.vue';
export default {
computed: {
lockIcon() {
return gl.utils.spriteIcon('lock');
},
component: {
Icon,
},
};
</script>
<template>
<div class="disabled-comment text-center">
<span class="issuable-note-warning">
<span class="icon" v-html="lockIcon"></span>
<span class="issuable-note-warning inline">
<icon
name="lock"
:size="16"
class="icon">
</icon>
<span>This issue is locked. Only <b>project members</b> can comment.</span>
</span>
</div>
......
......@@ -267,9 +267,11 @@
/>
<div
class="blank-state blank-state-no-icon"
class="blank-state-row"
v-if="shouldRenderNoPipelinesMessage">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div>
</div>
<div
......
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
/* global ProjectSelect */
import Cookies from 'js-cookie';
import projectSelect from './project_select';
export default class Project {
constructor() {
......@@ -46,7 +46,7 @@ export default class Project {
}
static projectSelectDropdown () {
new ProjectSelect();
projectSelect();
$('.project-item-select').on('click', e => Project.changeProject($(e.currentTarget).val()));
}
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, object-shorthand, comma-dangle, one-var, one-var-declaration-per-line, no-restricted-syntax, max-len, no-param-reassign */
export default class ProjectLabelSubscription {
constructor(container) {
this.$container = $(container);
this.$buttons = this.$container.find('.js-subscribe-button');
(function(global) {
class ProjectLabelSubscription {
constructor(container) {
this.$container = $(container);
this.$buttons = this.$container.find('.js-subscribe-button');
this.$buttons.on('click', this.toggleSubscription.bind(this));
}
this.$buttons.on('click', this.toggleSubscription.bind(this));
}
toggleSubscription(event) {
event.preventDefault();
toggleSubscription(event) {
event.preventDefault();
const $btn = $(event.currentTarget);
const $span = $btn.find('span');
const url = $btn.attr('data-url');
const oldStatus = $btn.attr('data-status');
const $btn = $(event.currentTarget);
const $span = $btn.find('span');
const url = $btn.attr('data-url');
const oldStatus = $btn.attr('data-status');
$btn.addClass('disabled');
$span.toggleClass('hidden');
$btn.addClass('disabled');
$span.toggleClass('hidden');
$.ajax({
type: 'POST',
url: url
}).done(() => {
let newStatus, newAction;
$.ajax({
type: 'POST',
url,
}).done(() => {
let newStatus;
let newAction;
if (oldStatus === 'unsubscribed') {
[newStatus, newAction] = ['subscribed', 'Unsubscribe'];
} else {
[newStatus, newAction] = ['unsubscribed', 'Subscribe'];
}
if (oldStatus === 'unsubscribed') {
[newStatus, newAction] = ['subscribed', 'Unsubscribe'];
} else {
[newStatus, newAction] = ['unsubscribed', 'Subscribe'];
}
$span.toggleClass('hidden');
$btn.removeClass('disabled');
$span.toggleClass('hidden');
$btn.removeClass('disabled');
this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction);
this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction);
this.$buttons.map((button) => {
const $button = $(button);
this.$buttons.map((button) => {
const $button = $(button);
if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
}
if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
}
return button;
});
return button;
});
}
});
}
global.ProjectLabelSubscription = ProjectLabelSubscription;
})(window.gl || (window.gl = {}));
}
This diff is collapsed.
......@@ -2,79 +2,73 @@
import Api from './api';
import ProjectSelectComboButton from './project_select_combo_button';
(function () {
this.ProjectSelect = (function () {
function ProjectSelect() {
$('.ajax-project-select').each(function(i, select) {
var placeholder;
const simpleFilter = $(select).data('simple-filter') || false;
this.groupId = $(select).data('group-id');
this.includeGroups = $(select).data('include-groups');
this.allProjects = $(select).data('all-projects') || false;
this.orderBy = $(select).data('order-by') || 'id';
this.withIssuesEnabled = $(select).data('with-issues-enabled');
this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
export default function projectSelect() {
$('.ajax-project-select').each(function(i, select) {
var placeholder;
const simpleFilter = $(select).data('simple-filter') || false;
this.groupId = $(select).data('group-id');
this.includeGroups = $(select).data('include-groups');
this.allProjects = $(select).data('all-projects') || false;
this.orderBy = $(select).data('order-by') || 'id';
this.withIssuesEnabled = $(select).data('with-issues-enabled');
this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
placeholder = "Search for project";
if (this.includeGroups) {
placeholder += " or group";
}
placeholder = "Search for project";
if (this.includeGroups) {
placeholder += " or group";
}
$(select).select2({
placeholder: placeholder,
minimumInputLength: 0,
query: (function (_this) {
return function (query) {
var finalCallback, projectsCallback;
finalCallback = function (projects) {
$(select).select2({
placeholder: placeholder,
minimumInputLength: 0,
query: (function (_this) {
return function (query) {
var finalCallback, projectsCallback;
finalCallback = function (projects) {
var data;
data = {
results: projects
};
return query.callback(data);
};
if (_this.includeGroups) {
projectsCallback = function (projects) {
var groupsCallback;
groupsCallback = function (groups) {
var data;
data = {
results: projects
};
return query.callback(data);
data = groups.concat(projects);
return finalCallback(data);
};
if (_this.includeGroups) {
projectsCallback = function (projects) {
var groupsCallback;
groupsCallback = function (groups) {
var data;
data = groups.concat(projects);
return finalCallback(data);
};
return Api.groups(query.term, {}, groupsCallback);
};
} else {
projectsCallback = finalCallback;
}
if (_this.groupId) {
return Api.groupProjects(_this.groupId, query.term, projectsCallback);
} else {
return Api.projects(query.term, {
order_by: _this.orderBy,
with_issues_enabled: _this.withIssuesEnabled,
with_merge_requests_enabled: _this.withMergeRequestsEnabled,
membership: !_this.allProjects,
}, projectsCallback);
}
return Api.groups(query.term, {}, groupsCallback);
};
})(this),
id: function(project) {
if (simpleFilter) return project.id;
return JSON.stringify({
name: project.name,
url: project.web_url,
});
},
text: function (project) {
return project.name_with_namespace || project.name;
},
dropdownCssClass: "ajax-project-dropdown"
} else {
projectsCallback = finalCallback;
}
if (_this.groupId) {
return Api.groupProjects(_this.groupId, query.term, projectsCallback);
} else {
return Api.projects(query.term, {
order_by: _this.orderBy,
with_issues_enabled: _this.withIssuesEnabled,
with_merge_requests_enabled: _this.withMergeRequestsEnabled,
membership: !_this.allProjects,
}, projectsCallback);
}
};
})(this),
id: function(project) {
if (simpleFilter) return project.id;
return JSON.stringify({
name: project.name,
url: project.web_url,
});
if (simpleFilter) return select;
return new ProjectSelectComboButton(select);
});
}
return ProjectSelect;
})();
}).call(window);
},
text: function (project) {
return project.name_with_namespace || project.name;
},
dropdownCssClass: "ajax-project-dropdown"
});
if (simpleFilter) return select;
return new ProjectSelectComboButton(select);
});
}
/* eslint-disable func-names, space-before-function-paren, wrap-iife */
(function() {
this.ProjectShow = (function() {
function ProjectShow() {}
return ProjectShow;
})();
}).call(window);
// I kept class for future
(() => {
const HIDDEN_VALUE_TEXT = '******';
class ProjectVariables {
constructor() {
this.$revealBtn = $('.js-btn-toggle-reveal-values');
this.$revealBtn.on('click', this.toggleRevealState.bind(this));
}
const HIDDEN_VALUE_TEXT = '******';
export default class ProjectVariables {
constructor() {
this.$revealBtn = $('.js-btn-toggle-reveal-values');
this.$revealBtn.on('click', this.toggleRevealState.bind(this));
}
toggleRevealState(e) {
e.preventDefault();
toggleRevealState(e) {
e.preventDefault();
const oldStatus = this.$revealBtn.attr('data-status');
let newStatus = 'hidden';
let newAction = 'Reveal Values';
const oldStatus = this.$revealBtn.attr('data-status');
let newStatus = 'hidden';
let newAction = 'Reveal Values';
if (oldStatus === 'hidden') {
newStatus = 'revealed';
newAction = 'Hide Values';
}
if (oldStatus === 'hidden') {
newStatus = 'revealed';
newAction = 'Hide Values';
}
this.$revealBtn.attr('data-status', newStatus);
this.$revealBtn.attr('data-status', newStatus);
const $variables = $('.variable-value');
const $variables = $('.variable-value');
$variables.each((_, variable) => {
const $variable = $(variable);
let newText = HIDDEN_VALUE_TEXT;
$variables.each((_, variable) => {
const $variable = $(variable);
let newText = HIDDEN_VALUE_TEXT;
if (newStatus === 'revealed') {
newText = $variable.attr('data-value');
}
if (newStatus === 'revealed') {
newText = $variable.attr('data-value');
}
$variable.text(newText);
});
$variable.text(newText);
});
this.$revealBtn.text(newAction);
}
this.$revealBtn.text(newAction);
}
window.gl = window.gl || {};
window.gl.ProjectVariables = ProjectVariables;
})();
}
......@@ -2,12 +2,13 @@
// Render Gitlab flavoured Markdown
//
// Delegates to syntax highlight and render math
// Delegates to syntax highlight and render math & mermaid diagrams.
//
(function() {
$.fn.renderGFM = function() {
this.find('.js-syntax-highlight').syntaxHighlight();
this.find('.js-render-math').renderMath();
this.find('.js-render-mermaid').renderMermaid();
return this;
};
......
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
//
// Example markup:
//
// <pre class="js-render-mermaid">
// graph TD;
// A-- > B;
// A-- > C;
// B-- > D;
// C-- > D;
// </pre>
//
import Flash from './flash';
$.fn.renderMermaid = function renderMermaid() {
if (this.length === 0) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
mermaid.initialize({
loadOnStart: false,
theme: 'neutral',
});
mermaid.init(undefined, this);
}).catch((err) => {
Flash(`Can't load mermaid module: ${err}`);
});
};
<script>
import Flash from '../../../flash';
import editForm from './edit_form.vue';
import Icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
editForm,
Icon,
},
props: {
isConfidential: {
......@@ -26,11 +28,8 @@ export default {
};
},
computed: {
faEye() {
const eye = this.isConfidential ? 'fa-eye-slash' : 'fa-eye';
return {
[eye]: true,
};
confidentialityIcon() {
return this.isConfidential ? 'eye-slash' : 'eye';
},
},
methods: {
......@@ -49,7 +48,11 @@ export default {
<template>
<div class="block issuable-sidebar-item confidentiality">
<div class="sidebar-collapsed-icon">
<i class="fa" :class="faEye" aria-hidden="true"></i>
<icon
:name="confidentialityIcon"
:size="16"
aria-hidden="true">
</icon>
</div>
<div class="title hide-collapsed">
Confidentiality
......@@ -70,11 +73,21 @@ export default {
:update-confidential-attribute="updateConfidentialAttribute"
/>
<div v-if="!isConfidential" class="no-value sidebar-item-value">
<i class="fa fa-eye sidebar-item-icon"></i>
<icon
name="eye"
:size="16"
aria-hidden="true"
class="sidebar-item-icon inline">
</icon>
Not confidential
</div>
<div v-else class="value sidebar-item-value hide-collapsed">
<i aria-hidden="true" class="fa fa-eye-slash sidebar-item-icon is-active"></i>
<icon
name="eye-slash"
:size="16"
aria-hidden="true"
class="sidebar-item-icon inline is-active">
</icon>
This issue is confidential
</div>
</div>
......
......@@ -2,6 +2,7 @@
/* global Flash */
import editForm from './edit_form.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable';
import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
......@@ -35,11 +36,12 @@ export default {
components: {
editForm,
Icon,
},
computed: {
lockIconClass() {
return this.isLocked ? 'fa-lock' : 'fa-unlock';
lockIcon() {
return this.isLocked ? 'lock' : 'lock-open';
},
isLockDialogOpen() {
......@@ -66,11 +68,12 @@ export default {
<template>
<div class="block issuable-sidebar-item lock">
<div class="sidebar-collapsed-icon">
<i
class="fa"
:class="lockIconClass"
<icon
:name="lockIcon"
:size="16"
aria-hidden="true"
></i>
class="sidebar-item-icon is-active">
</icon>
</div>
<div class="title hide-collapsed">
......@@ -98,10 +101,12 @@ export default {
v-if="isLocked"
class="value sidebar-item-value"
>
<i
<icon
name="lock"
:size="16"
aria-hidden="true"
class="fa fa-lock sidebar-item-icon is-active"
></i>
class="sidebar-item-icon inline is-active">
</icon>
{{ __('Locked') }}
</div>
......@@ -109,10 +114,12 @@ export default {
v-else
class="no-value sidebar-item-value hide-collapsed"
>
<i
<icon
name="lock-open"
:size="16"
aria-hidden="true"
class="fa fa-unlock sidebar-item-icon"
></i>
class="sidebar-item-icon inline">
</icon>
{{ __('Unlocked') }}
</div>
</div>
......
......@@ -3,6 +3,7 @@ import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub';
import Flash from '../../../flash';
import { __ } from '../../../locale';
import subscriptions from './subscriptions.vue';
export default {
......@@ -21,7 +22,7 @@ export default {
onToggleSubscription() {
this.mediator.toggleSubscription()
.catch(() => {
Flash('Error occurred when toggling the notification subscription');
Flash(__('Error occurred when toggling the notification subscription'));
});
},
},
......
......@@ -14,6 +14,10 @@ export default {
type: Boolean,
required: false,
},
id: {
type: Number,
required: false,
},
},
components: {
loadingButton,
......@@ -32,7 +36,7 @@ export default {
},
methods: {
toggleSubscription() {
eventHub.$emit('toggleSubscription');
eventHub.$emit('toggleSubscription', this.id);
},
},
};
......
class Subscription {
constructor(containerElm) {
this.containerElm = containerElm;
const subscribeButton = containerElm.querySelector('.js-subscribe-button');
if (subscribeButton) {
// remove class so we don't bind twice
subscribeButton.classList.remove('js-subscribe-button');
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
}
toggleSubscription(event) {
const button = event.currentTarget;
const buttonSpan = button.querySelector('span');
if (!buttonSpan || button.classList.contains('disabled')) {
return;
}
button.classList.add('disabled');
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
const toggleActionUrl = this.containerElm.dataset.url;
$.post(toggleActionUrl, () => {
button.classList.remove('disabled');
// hack to allow this to work with the issue boards Vue object
if (document.querySelector('html').classList.contains('issue-boards-page')) {
gl.issueBoards.boardStoreIssueSet(
'subscribed',
!gl.issueBoards.BoardsStore.detail.issue.subscribed,
);
} else {
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
}
});
}
static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
}
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
export default function subscriptionSelect() {
$('.js-subscription-event').each((i, element) => {
const fieldName = $(element).data('field-name');
class SubscriptionSelect {
constructor() {
$('.js-subscription-event').each(function(i, el) {
var fieldName;
fieldName = $(el).data("field-name");
return $(el).glDropdown({
selectable: true,
fieldName: fieldName,
toggleLabel: (function(_this) {
return function(selected, el, instance) {
var $item, label;
label = 'Subscription';
$item = instance.dropdown.find('.is-active');
if ($item.length) {
label = $item.text();
}
return label;
};
})(this),
clicked: function(options) {
return options.e.preventDefault();
},
id: function(obj, el) {
return $(el).data("id");
return $(element).glDropdown({
selectable: true,
fieldName,
toggleLabel(selected, el, instance) {
let label = 'Subscription';
const $item = instance.dropdown.find('.is-active');
if ($item.length) {
label = $item.text();
}
});
return label;
},
clicked(options) {
return options.e.preventDefault();
},
id(obj, el) {
return $(el).data('id');
},
});
}
});
}
window.SubscriptionSelect = SubscriptionSelect;
......@@ -6,10 +6,9 @@
Sample configuration:
<icon
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
name="retry"
:size="32"
css-classes="top"
/>
*/
......
<script>
import Icon from '../../../vue_shared/components/icon.vue';
export default {
props: {
isLocked: {
......@@ -14,12 +16,16 @@
},
},
components: {
Icon,
},
computed: {
iconClass() {
return {
'fa-eye-slash': this.isConfidential,
'fa-lock': this.isLocked,
};
warningIcon() {
if (this.isConfidential) return 'eye-slash';
if (this.isLocked) return 'lock';
return '';
},
isLockedAndConfidential() {
......@@ -30,12 +36,13 @@
</script>
<template>
<div class="issuable-note-warning">
<i
aria-hidden="true"
class="fa icon"
:class="iconClass"
v-if="!isLockedAndConfidential"
></i>
<icon
:name="warningIcon"
:size="16"
class="icon inline"
aria-hidden="true"
v-if="!isLockedAndConfidential">
</icon>
<span v-if="isLockedAndConfidential">
{{ __('This issue is confidential and locked.') }}
......
......@@ -25,6 +25,11 @@
type: String,
required: false,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
......@@ -129,6 +134,7 @@
<markdown-toolbar
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
/>
</div>
</div>
......
......@@ -50,7 +50,9 @@
<template>
<div class="md-header">
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }">
<li
class="md-header-tab"
:class="{ active: !previewMarkdown }">
<a
class="js-write-link"
href="#md-write-holder"
......@@ -59,7 +61,9 @@
Write
</a>
</li>
<li :class="{ active: previewMarkdown }">
<li
class="md-header-tab"
:class="{ active: previewMarkdown }">
<a
class="js-preview-link"
href="#md-preview-holder"
......@@ -68,56 +72,52 @@
Preview
</a>
</li>
<li class="pull-right">
<div class="toolbar-group">
<toolbar-button
tag="**"
button-title="Add bold text"
icon="bold" />
<toolbar-button
tag="*"
button-title="Add italic text"
icon="italic" />
<toolbar-button
tag="> "
:prepend="true"
button-title="Insert a quote"
icon="quote" />
<toolbar-button
tag="`"
tag-block="```"
button-title="Insert code"
icon="code" />
<toolbar-button
tag="* "
:prepend="true"
button-title="Add a bullet list"
icon="list-bulleted" />
<toolbar-button
tag="1. "
:prepend="true"
button-title="Add a numbered list"
icon="list-numbered" />
<toolbar-button
tag="* [ ] "
:prepend="true"
button-title="Add a task list"
icon="task-done" />
</div>
<div class="toolbar-group">
<button
v-tooltip
aria-label="Go full screen"
class="toolbar-btn js-zen-enter"
data-container="body"
tabindex="-1"
title="Go full screen"
type="button">
<icon
name="screen-full">
</icon>
</button>
</div>
<li class="md-header-toolbar">
<toolbar-button
tag="**"
button-title="Add bold text"
icon="bold" />
<toolbar-button
tag="*"
button-title="Add italic text"
icon="italic" />
<toolbar-button
tag="> "
:prepend="true"
button-title="Insert a quote"
icon="quote" />
<toolbar-button
tag="`"
tag-block="```"
button-title="Insert code"
icon="code" />
<toolbar-button
tag="* "
:prepend="true"
button-title="Add a bullet list"
icon="list-bulleted" />
<toolbar-button
tag="1. "
:prepend="true"
button-title="Add a numbered list"
icon="list-numbered" />
<toolbar-button
tag="* [ ] "
:prepend="true"
button-title="Add a task list"
icon="task-done" />
<button
v-tooltip
aria-label="Go full screen"
class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
data-container="body"
tabindex="-1"
title="Go full screen"
type="button">
<icon
name="screen-full">
</icon>
</button>
</li>
</ul>
</div>
......
......@@ -9,6 +9,11 @@
type: String,
required: false,
},
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
},
};
</script>
......@@ -41,7 +46,10 @@
are supported
</template>
</div>
<span class="uploading-container">
<span
v-if="canAttachFile"
class="uploading-container"
>
<span class="uploading-progress-container hide">
<i
class="fa fa-file-image-o toolbar-button-icon"
......
......@@ -40,7 +40,7 @@
<button
v-tooltip
type="button"
class="toolbar-btn js-md hidden-xs"
class="toolbar-btn js-md"
tabindex="-1"
data-container="body"
:data-md-tag="tag"
......
......@@ -56,6 +56,12 @@
}
}
.blank-state-center {
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
}
.blank-state {
padding: 20px;
border: 1px solid $border-color;
......@@ -66,7 +72,10 @@
align-items: center;
padding: 50px 30px;
}
}
.blank-state,
.blank-state-center {
.blank-state-icon {
svg {
display: block;
......
......@@ -2,7 +2,9 @@
.cgray { color: $common-gray; }
.clgray { color: $common-gray-light; }
.cred { color: $common-red; }
svg.cred { fill: $common-red; }
.cgreen { color: $common-green; }
svg.cgreen { fill: $common-green; }
.cdark { color: $common-gray-dark; }
.text-secondary {
color: $gl-text-color-secondary;
......@@ -428,6 +430,7 @@ img.emoji {
/** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-5 { margin-top: 5px; }
.prepend-top-8 { margin-top: $grid-size; }
.prepend-top-10 { margin-top: 10px; }
.prepend-top-15 { margin-top: 15px; }
.prepend-top-default { margin-top: $gl-padding !important; }
......
......@@ -40,12 +40,6 @@
a:hover {
background-color: $link-hover-background;
color: $gl-text-color;
.settings-avatar {
svg {
fill: $gl-text-color;
}
}
}
.avatar-container {
......@@ -138,10 +132,6 @@
color: $gl-text-color-secondary;
}
svg {
fill: $gl-text-color-secondary;
}
.nav-item-name {
flex: 1;
}
......@@ -224,10 +214,6 @@
&:hover {
color: $gl-text-color;
svg {
fill: $gl-text-color;
}
}
}
......@@ -338,7 +324,6 @@
align-items: center;
svg {
fill: $gl-text-color-secondary;
margin-right: 8px;
}
......@@ -349,10 +334,6 @@
&:hover {
background-color: $border-color;
color: $gl-text-color;
svg {
fill: $gl-text-color;
}
}
}
......
......@@ -305,16 +305,11 @@
color: $gl-text-color;
border-color: $dropdown-input-focus-border;
outline: none;
svg {
fill: $gl-text-color;
}
}
svg {
height: 14px;
width: 14px;
fill: $gl-text-color-secondary;
vertical-align: middle;
}
......
......@@ -30,10 +30,6 @@
&.dropdown.open > a {
color: $color-900;
background-color: $color-alternate;
svg {
fill: currentColor;
}
}
&.line-separator {
......@@ -51,10 +47,6 @@
color: $color-200;
> a {
svg {
fill: $color-200;
}
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $color-200;
......
......@@ -235,10 +235,6 @@
opacity: 1;
color: $white-light;
svg {
fill: currentColor;
}
&.header-user-dropdown-toggle .header-user-avatar {
border-color: $white-light;
}
......@@ -269,14 +265,6 @@
font-size: 20px;
}
}
&.active > a,
&.dropdown.open > a {
svg {
fill: currentColor;
}
}
}
}
}
......@@ -289,10 +277,6 @@
text-decoration: none;
outline: 0;
color: $white-light;
svg {
fill: currentColor;
}
}
> a {
......@@ -307,10 +291,6 @@
border-radius: $border-radius-default;
height: 32px;
font-weight: $gl-font-weight-bold;
svg {
fill: currentColor;
}
}
&.line-separator {
......
.ci-status-icon-success,
.ci-status-icon-passed {
color: $green-500;
svg {
fill: $green-500;
}
}
.ci-status-icon-failed {
color: $gl-danger;
svg {
fill: $gl-danger;
}
}
.ci-status-icon-pending,
.ci-status-icon-failed_with_warnings,
.ci-status-icon-success_with_warnings {
color: $orange-500;
svg {
fill: $orange-500;
}
}
.ci-status-icon-running {
color: $blue-400;
svg {
fill: $blue-400;
}
}
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found {
color: $gl-text-color;
svg {
fill: $gl-text-color;
}
}
.ci-status-icon-created,
.ci-status-icon-skipped {
color: $gray-darkest;
svg {
fill: $gray-darkest;
}
}
.ci-status-icon-manual {
color: $gl-text-color;
svg {
fill: $gl-text-color;
}
}
.icon-link {
......
......@@ -27,6 +27,8 @@
}
svg {
fill: currentColor;
&.s8 { @include svg-size(8px); }
&.s12 { @include svg-size(12px); }
&.s16 { @include svg-size(16px); }
......
......@@ -57,6 +57,7 @@
.md-header {
.nav-links {
a {
width: 100%;
padding-top: 0;
line-height: 19px;
......@@ -72,6 +73,28 @@
}
}
.md-header-tab {
@media(max-width: $screen-xs-max) {
flex: 1;
width: 100%;
border-bottom: 1px solid $border-color;
text-align: center;
}
}
.md-header-toolbar {
margin-left: auto;
@media(max-width: $screen-xs-max) {
flex: none;
display: flex;
justify-content: center;
width: 100%;
padding-top: $gl-padding-top;
padding-bottom: $gl-padding-top;
}
}
.referenced-users {
color: $gl-text-color;
padding-top: 10px;
......@@ -126,16 +149,6 @@
}
}
.toolbar-group {
float: left;
margin-right: -5px;
margin-left: $gl-padding;
&:first-child {
margin-left: 0;
}
}
.toolbar-btn {
float: left;
padding: 0 7px;
......@@ -158,6 +171,16 @@
}
}
.toolbar-fullscreen-btn {
margin-left: $gl-padding;
margin-right: -5px;
@media(max-width: $screen-xs-max) {
margin-left: 0;
margin-right: 0;
}
}
.atwho-view {
overflow-y: auto;
overflow-x: hidden;
......
......@@ -130,14 +130,6 @@
background-color: $color-light;
color: $color-dark;
border-color: $color-dark;
svg {
fill: $color-dark;
}
}
svg {
fill: $color-main;
}
}
......
......@@ -57,15 +57,7 @@
padding: 5px;
font-size: 36px;
svg {
fill: $gl-text-color;
}
&:hover {
color: $black;
svg {
fill: $black;
}
}
}
......@@ -49,6 +49,7 @@
font-size: 12px;
border-radius: 0;
border: 0;
padding: $grid-size;
.bash {
display: block;
......@@ -57,14 +58,13 @@
.top-bar {
height: 35px;
display: flex;
justify-content: flex-end;
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
position: sticky;
position: -webkit-sticky;
top: $header-height;
padding: $grid-size;
&.affix {
top: $header-height;
......@@ -90,9 +90,6 @@
}
.truncated-info {
margin: 0 auto;
align-self: center;
.truncated-info-size {
margin: 0 5px;
}
......@@ -118,7 +115,11 @@
.controllers-buttons {
color: $gl-text-color;
margin: 0 10px;
margin: 0 $grid-size;
&:last-child {
margin-right: 0;
}
}
.btn-scroll.animate {
......
......@@ -628,21 +628,46 @@
}
.diff-file-changes {
width: 450px;
max-width: 560px;
width: 100%;
z-index: 150;
@media (min-width: $screen-sm-min) {
left: $gl-padding;
}
a {
.diff-changed-file {
display: flex;
padding-top: 8px;
padding-bottom: 8px;
min-width: 0;
}
.diff-changed-file {
.diff-file-changed-icon {
margin-top: 2px;
}
.diff-changed-file-content {
display: flex;
align-items: center;
flex-direction: column;
min-width: 0;
}
.diff-changed-file-name,
.diff-changed-file-path {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.diff-changed-file-path {
direction: rtl;
color: $gl-text-color-tertiary;
}
.diff-changed-stats {
margin-left: auto;
white-space: nowrap;
}
}
......
.documentation-index {
h1 {
margin: 0;
}
h2 {
font-size: 20px;
}
li {
line-height: 24px;
color: $document-index-color;
a {
margin-right: 3px;
}
}
}
.shortcut-mappings {
font-size: 12px;
color: $help-shortcut-mapping-color;
......
......@@ -6,28 +6,20 @@
}
.issuable-warning-icon {
color: $orange-600;
background-color: $orange-100;
border-radius: $border-radius-default;
padding: 5px;
margin: 0 $btn-side-margin 0 0;
width: $issuable-warning-size;
height: $issuable-warning-size;
text-align: center;
&:first-of-type {
margin-right: $issuable-warning-icon-margin;
.icon {
fill: $orange-600;
vertical-align: text-bottom;
}
}
.sidebar-item-icon {
border-radius: $border-radius-default;
padding: 5px;
margin: 0 3px 0 -4px;
&.is-active {
color: $orange-600;
background-color: $orange-50;
&:first-of-type {
margin-right: $issuable-warning-icon-margin;
}
}
......
......@@ -113,6 +113,8 @@
.icon {
margin-right: $issuable-warning-icon-margin;
vertical-align: text-bottom;
fill: $orange-600;
}
+ .md-area {
......@@ -137,12 +139,24 @@
}
}
.sidebar-item-value {
.fa {
background-color: inherit;
.sidebar-item-icon {
border-radius: $border-radius-default;
margin: 0 3px 0 -4px;
vertical-align: middle;
&.is-active {
fill: $orange-600;
}
}
.sidebar-collapsed-icon .sidebar-item-icon {
margin: 0;
}
.sidebar-item-value .sidebar-item-icon {
fill: $theme-gray-700;
}
.sidebar-item-warning-message {
line-height: 1.5;
padding: 16px;
......
......@@ -547,10 +547,6 @@ ul.notes {
width: 16px;
top: 0;
vertical-align: text-top;
path {
fill: currentColor;
}
}
.award-control-icon-positive,
......@@ -570,10 +566,6 @@ ul.notes {
.link-highlight {
color: $gl-link-color;
fill: $gl-link-color;
svg {
fill: $gl-link-color;
}
}
.award-control-icon-neutral {
......
......@@ -55,10 +55,6 @@
&:not(span):hover {
background-color: rgba($gl-text-color-secondary, .07);
}
svg {
fill: $gl-text-color-secondary;
}
}
}
......
......@@ -11,8 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
before_action :authenticate_user_from_personal_access_token!
before_action :authenticate_user_from_rss_token!
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :check_password_expiration
......@@ -97,30 +96,15 @@ class ApplicationController < ActionController::Base
# (e.g. tokens) to authenticate the user, whereas Devise sets current_user
def auth_user
return current_user if current_user.present?
return try(:authenticated_user)
end
def authenticate_user_from_personal_access_token!
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
return unless token.present?
user = User.find_by_personal_access_token(token)
# This filter handles personal access tokens, and atom requests with rss tokens
def authenticate_sessionless_user!
user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user
sessionless_sign_in(user)
end
# This filter handles authentication for atom request with an rss_token
def authenticate_user_from_rss_token!
return unless request.format.atom?
token = params[:rss_token].presence
return unless token.present?
user = User.find_by_rss_token(token)
sessionless_sign_in(user)
sessionless_sign_in(user) if user
end
def log_exception(exception)
......@@ -212,7 +196,11 @@ class ApplicationController < ActionController::Base
end
def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
return if session[:impersonator_id] || current_user&.ldap_user?
password_expires_at = current_user&.password_expires_at
if password_expires_at && password_expires_at < Time.now
return redirect_to new_profile_password_path
end
end
......
......@@ -44,6 +44,7 @@ class AutocompleteController < ApplicationController
if @project.blank? && params[:group_id].present?
group = Group.find(params[:group_id])
return render_404 unless can?(current_user, :read_group, group)
group
end
end
......@@ -54,6 +55,7 @@ class AutocompleteController < ApplicationController
if params[:project_id].present?
project = Project.find(params[:project_id])
return render_404 unless can?(current_user, :read_project, project)
project
end
end
......
......@@ -84,6 +84,7 @@ module Boards
resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
labels: true,
sidebar_endpoints: true,
include: {
project: { only: [:id, :path] },
assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
......
......@@ -4,7 +4,7 @@ module NotesActions
included do
before_action :set_polling_interval_header, only: [:index]
before_action :noteable, only: :index
before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create]
end
......@@ -90,7 +90,7 @@ module NotesActions
if note.persisted?
attrs[:valid] = true
if noteable.nil? || noteable.discussions_rendered_on_frontend?
if noteable.discussions_rendered_on_frontend?
attrs.merge!(note_serializer.represent(note))
else
attrs.merge!(
......@@ -191,7 +191,11 @@ module NotesActions
end
def noteable
@noteable ||= notes_finder.target || render_404
@noteable ||= notes_finder.target || @note&.noteable
end
def require_noteable!
render_404 unless noteable
end
def last_fetched_at
......
......@@ -80,7 +80,8 @@ class Groups::MilestonesController < Groups::ApplicationController
milestones = MilestonesFinder.new(search_params).execute
legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
milestones + legacy_milestones
@sort = params[:sort] || 'due_date_asc'
MilestoneArray.sort(milestones + legacy_milestones, @sort)
end
def milestone
......
......@@ -4,6 +4,7 @@ class Import::GitlabProjectsController < Import::BaseController
def new
@namespace = Namespace.find(project_params[:namespace_id])
return render_404 unless current_user.can?(:create_projects, @namespace)
@path = project_params[:path]
end
......
......@@ -54,7 +54,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if current_user
log_audit_event(current_user, with: :saml)
# Update SAML identity if data has changed.
identity = current_user.identities.find_by(extern_uid: oauth['uid'], provider: :saml)
identity = current_user.identities.with_extern_uid(:saml, oauth['uid']).take
if identity.nil?
current_user.identities.create(extern_uid: oauth['uid'], provider: :saml)
redirect_to profile_account_path, notice: 'Authentication method updated'
......@@ -98,7 +98,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth
if current_user
# Add new authentication method
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
current_user.identities
.with_extern_uid(oauth['provider'], oauth['uid'])
.first_or_create(extern_uid: oauth['uid'])
log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
......
......@@ -22,12 +22,7 @@ class Projects::CommitController < Projects::ApplicationController
apply_diff_view_cookie!
respond_to do |format|
format.html do
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37599
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
end
format.html { render }
format.diff { render text: @commit.to_diff }
format.patch { render text: @commit.to_patch }
end
......@@ -112,7 +107,7 @@ class Projects::CommitController < Projects::ApplicationController
end
def commit
@noteable = @commit ||= @project.commit(params[:id])
@noteable = @commit ||= @project.commit_by(oid: params[:id])
end
def define_commit_vars
......
......@@ -57,6 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
@commits = @commits.with_pipeline_status
@commits = prepare_commits_for_rendering(@commits)
end
end
......@@ -12,6 +12,7 @@ class Projects::DeploymentsController < Projects::ApplicationController
def metrics
return render_404 unless deployment.has_metrics?
@metrics = deployment.metrics
if @metrics&.any?
render json: @metrics, status: :ok
......
......@@ -12,6 +12,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
if group
return render_404 unless can?(current_user, :read_group, group)
Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
else
flash[:alert] = 'Please select a group.'
......
......@@ -171,6 +171,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue
return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by
@issuable = @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
@note = @project.notes.new(noteable: @issuable)
......
......@@ -111,6 +111,7 @@ class Projects::LabelsController < Projects::ApplicationController
begin
return render_404 unless promote_service.execute(@label)
respond_to do |format|
format.html do
redirect_to(project_labels_path(@project),
......
......@@ -54,6 +54,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
name = request.headers['X-Gitlab-Lfs-Tmp']
return if name.include?('/')
return unless oid.present? && name.start_with?(oid)
name
end
......
......@@ -10,10 +10,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def show
@environment = @merge_request.environments_for(current_user).last
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37431
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
end
render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
end
def diff_for_path
......
......@@ -80,7 +80,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def commits
# Get commits from repository
# or from cache if already merged
@commits = prepare_commits_for_rendering(@merge_request.commits)
@commits =
prepare_commits_for_rendering(@merge_request.commits.with_pipeline_status)
render json: { html: view_to_html_string('projects/merge_requests/_commits') }
end
......
......@@ -76,6 +76,7 @@ class Projects::NotesController < Projects::ApplicationController
def authorize_create_note!
return unless noteable.lockable?
access_denied! unless can?(current_user, :create_note, noteable)
end
end
......@@ -28,6 +28,7 @@ class Projects::WikisController < Projects::ApplicationController
)
else
return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPage.new(@project_wiki)
@page.title = params[:id]
......@@ -74,7 +75,11 @@ class Projects::WikisController < Projects::ApplicationController
def history
@page = @project_wiki.find_page(params[:id])
unless @page
if @page
@page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]),
total_count: @page.count_versions)
.page(params[:page])
else
redirect_to(
project_wiki_path(@project, :home),
notice: "Page not found"
......@@ -101,7 +106,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15))
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15))
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
......
......@@ -269,6 +269,7 @@ class ProjectsController < Projects::ApplicationController
def render_landing_page
if can?(current_user, :download_code, @project)
return render 'projects/no_repo' unless @project.repository_exists?
render 'projects/empty' if @project.empty_repo?
else
if @project.wiki_enabled?
......
......@@ -20,6 +20,7 @@ class Snippets::NotesController < ApplicationController
def snippet
PersonalSnippet.find_by(id: params[:snippet_id])
end
alias_method :noteable, :snippet
def note_params
super.merge(noteable_id: params[:snippet_id])
......
......@@ -18,6 +18,7 @@ class PersonalAccessTokensFinder
def by_user(tokens)
return tokens unless @params[:user]
tokens.where(user: @params[:user])
end
......
......@@ -30,4 +30,11 @@ module AppearancesHelper
render 'shared/logo.svg'
end
end
# Skip the 'GitLab' type logo when custom brand logo is set
def brand_header_logo_type
unless brand_item && brand_item.header_logo?
render 'shared/logo_type.svg'
end
end
end
......@@ -231,6 +231,15 @@ module ApplicationSettingsHelper
:sign_in_text,
:signup_enabled,
:terminal_max_session_time,
:throttle_unauthenticated_enabled,
:throttle_unauthenticated_requests_per_period,
:throttle_unauthenticated_period_in_seconds,
:throttle_authenticated_web_enabled,
:throttle_authenticated_web_requests_per_period,
:throttle_authenticated_web_period_in_seconds,
:throttle_authenticated_api_enabled,
:throttle_authenticated_api_requests_per_period,
:throttle_authenticated_api_period_in_seconds,
:two_factor_grace_period,
:unique_ips_limit_enabled,
:unique_ips_limit_per_user,
......
......@@ -6,11 +6,6 @@
# See 'detailed_status?` method and `Gitlab::Ci::Status` module.
#
module CiStatusHelper
def ci_status_path(pipeline)
project = pipeline.project
project_pipeline_path(project, pipeline)
end
def ci_label_for_status(status)
if detailed_status?(status)
return status.label
......
......@@ -111,6 +111,7 @@ module DiffHelper
def diff_file_old_blob_raw_path(diff_file)
sha = diff_file.old_content_sha
return unless sha
project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path))
end
......@@ -152,11 +153,11 @@ module DiffHelper
def diff_file_changed_icon(diff_file)
if diff_file.deleted_file? || diff_file.renamed_file?
"minus"
"file-deletion"
elsif diff_file.new_file?
"plus"
"file-addition"
else
"adjust"
"file-modified"
end
end
......
......@@ -24,6 +24,7 @@ module EmailsHelper
def action_title(url)
return unless url
%w(merge_requests issues commit).each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
......
......@@ -53,6 +53,7 @@ module MarkupHelper
# text, wrapping anything found in the requested link
fragment.children.each do |node|
next unless node.text?
node.replace(link_to(node.text, url, html_options))
end
end
......@@ -221,7 +222,7 @@ module MarkupHelper
data = options[:data].merge({ container: 'body' })
content_tag :button,
type: 'button',
class: 'toolbar-btn js-md has-tooltip hidden-xs',
class: 'toolbar-btn js-md has-tooltip',
tabindex: -1,
data: data,
title: options[:title],
......
......@@ -78,6 +78,7 @@ module NotificationsHelper
# Create hidden field to send notification setting source to controller
def hidden_setting_source_input(notification_setting)
return unless notification_setting.source_type
hidden_field_tag "#{notification_setting.source_type.downcase}_id", notification_setting.source_id
end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment