Commit cf76a6c1 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into ee-38869-groups-select

* master: (139 commits)
  btn-small -> btn-sm
  Merge branch 'fix-application-setting-nil-cache' into 'master'
  no CI replication
  [CE->EE] Resove frontent conflicts
  [CE->EE] Fix wrong resolvation of specs
  [CE->EE] Resolve group routes
  [CE->EE][ci skip] Resolve conflicts
  Resolve conflict in lib/backup/manager.rb
  Resolve conflict in .gitlab-ci.yml
  [CE->EE][ci skip] Resolve conflicts
  [CE->EE][ci skip] resolve frontend conflicts
  Remove `<script>` and `<template>` from CHANGELOG.md
  Simple docs fixes
  Fix button type
  Changes after review
  Removes group_avatar & group_label_subscription from global namespace
  Fix CSS in load more participants
  Fix the description of the new branch created by an issue's new branch button
  Fix a small typo in the delete merged branches documentation
  Moves vue resource docs into a new file. Adds docs regarding handling 204 response
  ...
parents 465f95a8 142ecc58
......@@ -52,11 +52,11 @@ stages:
- gitlab-org
.tests-metadata-state: &tests-metadata-state
services: []
<<: *dedicated-runner
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
before_script:
- source scripts/utils.sh
artifacts:
expire_in: 31d
paths:
......@@ -92,6 +92,7 @@ stages:
.rspec-metadata: &rspec-metadata
<<: *dedicated-runner
<<: *pull-cache
<<: *except-docs
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
......@@ -121,7 +122,6 @@ stages:
.rspec-metadata-pg: &rspec-metadata-pg
<<: *rspec-metadata
<<: *use-pg
<<: *except-docs
.rspec-geo-pg-9-6: &rspec-metadata-pg-geo
<<: *use-pg-9-6
......@@ -137,11 +137,11 @@ stages:
.rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata
<<: *use-mysql
<<: *except-docs
.spinach-metadata: &spinach-metadata
<<: *dedicated-runner
<<: *pull-cache
<<: *except-docs
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
......@@ -164,12 +164,10 @@ stages:
.spinach-metadata-pg: &spinach-metadata-pg
<<: *spinach-metadata
<<: *use-pg
<<: *except-docs
.spinach-metadata-mysql: &spinach-metadata-mysql
<<: *spinach-metadata
<<: *use-mysql
<<: *except-docs
.only-canonical-masters: &only-canonical-masters
only:
......@@ -180,13 +178,8 @@ stages:
# Trigger a package build in omnibus-gitlab repository
build-package:
image: ruby:2.3-alpine
image: ruby:2.4-alpine
before_script: []
services: []
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
EE_PACKAGE: "true"
stage: build
cache: {}
when: manual
......@@ -207,13 +200,9 @@ build-package:
- apk add --update openssl
- wget https://gitlab.com/gitlab-org/gitlab-ce/raw/master/scripts/trigger-build-docs
- chmod 755 trigger-build-docs
services: []
cache: {}
dependencies: []
artifacts: {}
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
GIT_STRATEGY: none
when: manual
only:
......@@ -246,7 +235,6 @@ review-docs-cleanup:
# Retrieve knapsack and rspec_flaky reports
retrieve-tests-metadata:
<<: *tests-metadata-state
<<: *dedicated-runner
<<: *except-docs
stage: prepare
cache:
......@@ -264,7 +252,6 @@ retrieve-tests-metadata:
update-tests-metadata:
<<: *tests-metadata-state
<<: *dedicated-runner
<<: *only-canonical-masters
stage: post-test
cache:
......@@ -329,71 +316,71 @@ setup-test-env:
- public/assets
- tmp/tests
rspec-pg 0 25: *rspec-metadata-pg
rspec-pg 1 25: *rspec-metadata-pg
rspec-pg 2 25: *rspec-metadata-pg
rspec-pg 3 25: *rspec-metadata-pg
rspec-pg 4 25: *rspec-metadata-pg
rspec-pg 5 25: *rspec-metadata-pg
rspec-pg 6 25: *rspec-metadata-pg
rspec-pg 7 25: *rspec-metadata-pg
rspec-pg 8 25: *rspec-metadata-pg
rspec-pg 9 25: *rspec-metadata-pg
rspec-pg 10 25: *rspec-metadata-pg
rspec-pg 11 25: *rspec-metadata-pg
rspec-pg 12 25: *rspec-metadata-pg
rspec-pg 13 25: *rspec-metadata-pg
rspec-pg 14 25: *rspec-metadata-pg
rspec-pg 15 25: *rspec-metadata-pg
rspec-pg 16 25: *rspec-metadata-pg
rspec-pg 17 25: *rspec-metadata-pg
rspec-pg 18 25: *rspec-metadata-pg
rspec-pg 19 25: *rspec-metadata-pg
rspec-pg 20 25: *rspec-metadata-pg
rspec-pg 21 25: *rspec-metadata-pg
rspec-pg 22 25: *rspec-metadata-pg
rspec-pg 23 25: *rspec-metadata-pg
rspec-pg 24 25: *rspec-metadata-pg
rspec-pg geo: *rspec-metadata-pg-geo
rspec-mysql 0 25: *rspec-metadata-mysql
rspec-mysql 1 25: *rspec-metadata-mysql
rspec-mysql 2 25: *rspec-metadata-mysql
rspec-mysql 3 25: *rspec-metadata-mysql
rspec-mysql 4 25: *rspec-metadata-mysql
rspec-mysql 5 25: *rspec-metadata-mysql
rspec-mysql 6 25: *rspec-metadata-mysql
rspec-mysql 7 25: *rspec-metadata-mysql
rspec-mysql 8 25: *rspec-metadata-mysql
rspec-mysql 9 25: *rspec-metadata-mysql
rspec-mysql 10 25: *rspec-metadata-mysql
rspec-mysql 11 25: *rspec-metadata-mysql
rspec-mysql 12 25: *rspec-metadata-mysql
rspec-mysql 13 25: *rspec-metadata-mysql
rspec-mysql 14 25: *rspec-metadata-mysql
rspec-mysql 15 25: *rspec-metadata-mysql
rspec-mysql 16 25: *rspec-metadata-mysql
rspec-mysql 17 25: *rspec-metadata-mysql
rspec-mysql 18 25: *rspec-metadata-mysql
rspec-mysql 19 25: *rspec-metadata-mysql
rspec-mysql 20 25: *rspec-metadata-mysql
rspec-mysql 21 25: *rspec-metadata-mysql
rspec-mysql 22 25: *rspec-metadata-mysql
rspec-mysql 23 25: *rspec-metadata-mysql
rspec-mysql 24 25: *rspec-metadata-mysql
spinach-pg 0 5: *spinach-metadata-pg
spinach-pg 1 5: *spinach-metadata-pg
spinach-pg 2 5: *spinach-metadata-pg
spinach-pg 3 5: *spinach-metadata-pg
spinach-pg 4 5: *spinach-metadata-pg
spinach-mysql 0 5: *spinach-metadata-mysql
spinach-mysql 1 5: *spinach-metadata-mysql
spinach-mysql 2 5: *spinach-metadata-mysql
spinach-mysql 3 5: *spinach-metadata-mysql
spinach-mysql 4 5: *spinach-metadata-mysql
rspec-pg 0 26: *rspec-metadata-pg
rspec-pg 1 26: *rspec-metadata-pg
rspec-pg 2 26: *rspec-metadata-pg
rspec-pg 3 26: *rspec-metadata-pg
rspec-pg 4 26: *rspec-metadata-pg
rspec-pg 5 26: *rspec-metadata-pg
rspec-pg 6 26: *rspec-metadata-pg
rspec-pg 7 26: *rspec-metadata-pg
rspec-pg 8 26: *rspec-metadata-pg
rspec-pg 9 26: *rspec-metadata-pg
rspec-pg 10 26: *rspec-metadata-pg
rspec-pg 11 26: *rspec-metadata-pg
rspec-pg 12 26: *rspec-metadata-pg
rspec-pg 13 26: *rspec-metadata-pg
rspec-pg 14 26: *rspec-metadata-pg
rspec-pg 15 26: *rspec-metadata-pg
rspec-pg 16 26: *rspec-metadata-pg
rspec-pg 17 26: *rspec-metadata-pg
rspec-pg 18 26: *rspec-metadata-pg
rspec-pg 19 26: *rspec-metadata-pg
rspec-pg 20 26: *rspec-metadata-pg
rspec-pg 21 26: *rspec-metadata-pg
rspec-pg 22 26: *rspec-metadata-pg
rspec-pg 23 26: *rspec-metadata-pg
rspec-pg 24 26: *rspec-metadata-pg
rspec-pg 25 26: *rspec-metadata-pg
rspec-mysql 0 26: *rspec-metadata-mysql
rspec-mysql 1 26: *rspec-metadata-mysql
rspec-mysql 2 26: *rspec-metadata-mysql
rspec-mysql 3 26: *rspec-metadata-mysql
rspec-mysql 4 26: *rspec-metadata-mysql
rspec-mysql 5 26: *rspec-metadata-mysql
rspec-mysql 6 26: *rspec-metadata-mysql
rspec-mysql 7 26: *rspec-metadata-mysql
rspec-mysql 8 26: *rspec-metadata-mysql
rspec-mysql 9 26: *rspec-metadata-mysql
rspec-mysql 10 26: *rspec-metadata-mysql
rspec-mysql 11 26: *rspec-metadata-mysql
rspec-mysql 12 26: *rspec-metadata-mysql
rspec-mysql 13 26: *rspec-metadata-mysql
rspec-mysql 14 26: *rspec-metadata-mysql
rspec-mysql 15 26: *rspec-metadata-mysql
rspec-mysql 16 26: *rspec-metadata-mysql
rspec-mysql 17 26: *rspec-metadata-mysql
rspec-mysql 18 26: *rspec-metadata-mysql
rspec-mysql 19 26: *rspec-metadata-mysql
rspec-mysql 20 26: *rspec-metadata-mysql
rspec-mysql 21 26: *rspec-metadata-mysql
rspec-mysql 22 26: *rspec-metadata-mysql
rspec-mysql 23 26: *rspec-metadata-mysql
rspec-mysql 24 26: *rspec-metadata-mysql
rspec-mysql 25 26: *rspec-metadata-mysql
spinach-pg 0 4: *spinach-metadata-pg
spinach-pg 1 4: *spinach-metadata-pg
spinach-pg 2 4: *spinach-metadata-pg
spinach-pg 3 4: *spinach-metadata-pg
spinach-mysql 0 4: *spinach-metadata-mysql
spinach-mysql 1 4: *spinach-metadata-mysql
spinach-mysql 2 4: *spinach-metadata-mysql
spinach-mysql 3 4: *spinach-metadata-mysql
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
......
......@@ -626,7 +626,7 @@ Style/PredicateName:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
Max: 55.25
Max: 54.28
# This cop checks if the length of a block exceeds some maximum value.
Metrics/BlockLength:
......@@ -667,7 +667,7 @@ Metrics/ParameterLists:
# A complexity metric geared towards measuring complexity for a human reader.
Metrics/PerceivedComplexity:
Enabled: true
Max: 15
Max: 14
# Lint ########################################################################
......
0.47.0
0.49.0
\ No newline at end of file
......@@ -105,7 +105,7 @@ gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.1.0'
gem 'fog-aliyun', '~> 0.2.0'
# for Google storage
gem 'google-api-client', '~> 0.13.6'
......@@ -414,7 +414,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.42.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.45.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -238,7 +238,7 @@ GEM
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
fog-aliyun (0.1.0)
fog-aliyun (0.2.0)
fog-core (~> 1.27)
fog-json (~> 1.0)
ipaddress (~> 0.8)
......@@ -297,7 +297,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.42.0)
gitaly-proto (0.45.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -1050,7 +1050,7 @@ DEPENDENCIES
flay (~> 2.8.0)
flipper (~> 0.10.2)
flipper-active_record (~> 0.10.2)
fog-aliyun (~> 0.1.0)
fog-aliyun (~> 0.2.0)
fog-aws (~> 1.4)
fog-core (~> 1.44)
fog-google (~> 0.5)
......@@ -1065,7 +1065,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.42.0)
gitaly-proto (~> 0.45.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......
# GitLab Maintenance Policy
GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
`(Major).(Minor).(Patch)` in a [pragmatic way].
- **Major version**: Whenever there is something significant or any backwards
incompatible changes are introduced to the public API.
- **Minor version**: When new, backwards compatible functionality is introduced
to the public API or a minor feature is introduced, or when a set of smaller
features is rolled out.
- **Patch number**: When backwards compatible bug fixes are introduced that fix
incorrect behavior.
The current stable release will receive security patches and bug fixes
(eg. `8.9.0` -> `8.9.1`). Feature releases will mark the next supported stable
release where the minor version is increased numerically by increments of one
(eg. `8.9 -> 8.10`).
Our current policy is to support one stable release at any given time, but for
medium-level security issues, we may consider [backporting to the previous two
monthly releases][rel-sec].
We encourage everyone to run the latest stable release to ensure that you can
easily upgrade to the most secure and feature-rich GitLab experience. In order
to make sure you can easily run the most recent stable release, we are working
hard to keep the update process simple and reliable.
More information about the release procedures can be found in our
[release-tools documentation][rel]. You may also want to read our
[Responsible Disclosure Policy][disclosure].
[rel-sec]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/security.md#backporting
[rel]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/
[disclosure]: https://about.gitlab.com/disclosure/
[pragmatic way]: https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e
See [doc/policy/maintenance.md](doc/policy/maintenance.md)
10.1.0-pre
10.2.0-pre
/* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props, no-new */
/* global GroupsSelect */
/* global ProjectSelect */
import UsersSelect from './users_select';
import './groups_select';
import groupsSelect from './groups_select';
import './project_select';
class AuditLogs {
......@@ -13,7 +12,7 @@ class AuditLogs {
initFilters() {
new ProjectSelect();
new GroupsSelect();
groupsSelect();
new UsersSelect();
this.initFilterDropdown($('.js-type-filter'), 'event_type', null, () => {
......
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */
/* global Dropzone */
import Dropzone from 'dropzone';
import '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
......
......@@ -25,6 +25,11 @@
type: String,
required: true,
},
viewType: {
type: String,
required: false,
default: 'child',
},
},
mixins: [
pipelinesMixin,
......@@ -110,6 +115,7 @@
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
</div>
</div>
......
/* eslint-disable class-methods-use-this */
import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff';
......@@ -8,7 +6,7 @@ import imageDiffHelper from './image_diff/helpers/index';
const UNFOLD_COUNT = 20;
let isBound = false;
class Diff {
export default class Diff {
constructor() {
const $diffFile = $('.files .diff-file');
......@@ -104,7 +102,7 @@ class Diff {
}
this.highlightSelectedLine();
}
// eslint-disable-next-line class-methods-use-this
handleParallelLineDown(e) {
const line = $(e.currentTarget);
const table = line.closest('table');
......@@ -116,11 +114,11 @@ class Diff {
table.addClass(`${lineClass}-selected`);
}
}
// eslint-disable-next-line class-methods-use-this
diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type');
}
// eslint-disable-next-line class-methods-use-this
lineNumbers(line) {
const children = line.find('.diff-line-num').toArray();
if (children.length !== 2) {
......@@ -128,7 +126,7 @@ class Diff {
}
return children.map(elm => parseInt($(elm).data('linenumber'), 10) || 0);
}
// eslint-disable-next-line class-methods-use-this
highlightSelectedLine() {
const hash = gl.utils.getLocationHash();
const $diffFiles = $('.diff-file');
......@@ -141,6 +139,3 @@ class Diff {
}
}
}
window.gl = window.gl || {};
window.gl.Diff = Diff;
......@@ -8,7 +8,8 @@
/* global NewBranchForm */
/* global NotificationsForm */
/* global NotificationsDropdown */
/* global GroupAvatar */
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
/* global LineHighlighter */
import BuildArtifacts from './build_artifacts';
import CILintEditor from './ci_lint_editor';
......@@ -90,6 +91,7 @@ import U2FAuthenticate from './u2f/authenticate';
import Members from './members';
import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select';
import Diff from './diff';
// EE-only
import ApproversSelect from './approvers_select';
......@@ -262,7 +264,7 @@ import initGroupAnalytics from './init_group_analytics';
new GLForm($('.milestone-form'), true);
break;
case 'projects:compare:show':
new gl.Diff();
new Diff();
initChangesDropdown();
break;
case 'projects:branches:new':
......@@ -300,7 +302,7 @@ import initGroupAnalytics from './init_group_analytics';
new UserCallout();
case 'projects:merge_requests:creations:diffs':
case 'projects:merge_requests:edit':
new gl.Diff();
new Diff();
shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form'), true);
new IssuableForm($('.merge-request-form'));
......@@ -334,7 +336,7 @@ import initGroupAnalytics from './init_group_analytics';
new GLForm($('.release-form'), true);
break;
case 'projects:merge_requests:show':
new gl.Diff();
new Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode();
......@@ -350,7 +352,7 @@ import initGroupAnalytics from './init_group_analytics';
new gl.Activities();
break;
case 'projects:commit:show':
new gl.Diff();
new Diff();
new ZenMode();
shortcut_handler = new ShortcutsNavigation();
new MiniPipelineGraph({
......@@ -398,7 +400,7 @@ import initGroupAnalytics from './init_group_analytics';
break;
case 'projects:edit':
new UsersSelect();
new GroupsSelect();
groupsSelect();
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
......@@ -459,11 +461,11 @@ import initGroupAnalytics from './init_group_analytics';
case 'admin:groups:create':
BindInOut.initAll();
new Group();
new GroupAvatar();
groupAvatar();
break;
case 'groups:edit':
case 'admin:groups:edit':
new GroupAvatar();
groupAvatar();
break;
case 'projects:tree:show':
shortcut_handler = new ShortcutsNavigation();
......@@ -518,7 +520,7 @@ import initGroupAnalytics from './init_group_analytics';
const $el = $(el);
if ($el.find('.dropdown-group-label').length) {
new gl.GroupLabelSubscription($el);
new GroupLabelSubscription($el);
} else {
new gl.ProjectLabelSubscription($el);
}
......
This diff is collapsed.
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
/* global notes */
/* Developer beware! Do not add logic to showButton or hideButton
* that will force a reflow. Doing so will create a signficant performance
* bottleneck for pages with large diffs. For a comprehensive list of what
......@@ -20,8 +17,10 @@ const DIFF_EXPANDED_CLASS = 'diff-expanded';
export default {
init($diffFile) {
/* Caching is used only when the following members are *true*. This is because there are likely to be
* differently configured versions of diffs in the same session. However if these values are true, they
/* Caching is used only when the following members are *true*.
* This is because there are likely to be
* differently configured versions of diffs in the same session.
* However if these values are true, they
* will be true in all cases */
if (!this.userCanCreateNote) {
......
/* global DropzoneInput */
/* global autosize */
import GfmAutoComplete from './gfm_auto_complete';
import dropzoneInput from './dropzone_input';
export default class GLForm {
constructor(form, enableGFM = false) {
......@@ -41,7 +41,7 @@ export default class GLForm {
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
new DropzoneInput(this.form); // eslint-disable-line no-new
dropzoneInput(this.form);
autosize(this.textarea);
}
// form and textarea event listeners
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */
window.GroupAvatar = (function() {
function GroupAvatar() {
$('.js-choose-group-avatar-button').on("click", function() {
var form;
form = $(this).closest("form");
return form.find(".js-group-avatar-input").click();
});
$('.js-group-avatar-input').on("change", function() {
var filename, form;
form = $(this).closest("form");
filename = $(this).val().replace(/^.*[\\\/]/, '');
return form.find(".js-avatar-filename").text(filename);
});
}
return GroupAvatar;
})();
export default function groupAvatar() {
$('.js-choose-group-avatar-button').on('click', function onClickGroupAvatar() {
const form = $(this).closest('form');
return form.find('.js-group-avatar-input').click();
});
$('.js-group-avatar-input').on('change', function onChangeAvatarInput() {
const form = $(this).closest('form');
// eslint-disable-next-line no-useless-escape
const filename = $(this).val().replace(/^.*[\\\/]/, '');
return form.find('.js-avatar-filename').text(filename);
});
}
/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */
class GroupLabelSubscription {
export default class GroupLabelSubscription {
constructor(container) {
const $container = $(container);
this.$dropdown = $container.find('.dropdown');
......@@ -18,7 +16,7 @@ class GroupLabelSubscription {
$.ajax({
type: 'POST',
url: url
url,
}).done(() => {
this.toggleSubscriptionButtons();
this.$unsubscribeButtons.removeAttr('data-url');
......@@ -35,7 +33,7 @@ class GroupLabelSubscription {
$.ajax({
type: 'POST',
url: url
url,
}).done(() => {
this.toggleSubscriptionButtons();
});
......@@ -47,6 +45,3 @@ class GroupLabelSubscription {
this.$unsubscribeButtons.toggleClass('hidden');
}
}
window.gl = window.gl || {};
window.gl.GroupLabelSubscription = GroupLabelSubscription;
......@@ -119,7 +119,7 @@ export default {
v-if="canAddRelatedIssues"
ref="issueCountBadgeAddButton"
type="button"
class="js-issue-count-badge-add-button issue-count-badge-add-button btn btn-small btn-default"
class="js-issue-count-badge-add-button issue-count-badge-add-button btn btn-sm btn-default"
aria-label="Add an issue"
data-placement="top"
@click="toggleAddRelatedIssuesForm">
......
......@@ -51,20 +51,19 @@ const PARTICIPANTS_ROW_COUNT = 7;
}
IssuableContext.prototype.initParticipants = function() {
$(document).on("click", ".js-participants-more", this.toggleHiddenParticipants);
return $(".js-participants-author").each(function(i) {
$(document).on('click', '.js-participants-more', this.toggleHiddenParticipants);
return $('.js-participants-author').each(function(i) {
if (i >= PARTICIPANTS_ROW_COUNT) {
return $(this).addClass("js-participants-hidden").hide();
return $(this).addClass('js-participants-hidden').hide();
}
});
};
IssuableContext.prototype.toggleHiddenParticipants = function(e) {
var currentText, lessText, originalText;
e.preventDefault();
currentText = $(this).text().trim();
lessText = $(this).data("less-text");
originalText = $(this).data("original-text");
IssuableContext.prototype.toggleHiddenParticipants = function() {
const currentText = $(this).text().trim();
const lessText = $(this).data('less-text');
const originalText = $(this).data('original-text');
if (currentText === originalText) {
$(this).text(lessText);
......@@ -73,7 +72,7 @@ const PARTICIPANTS_ROW_COUNT = 7;
$(this).text(originalText);
}
$(".js-participants-hidden").toggle();
$('.js-participants-hidden').toggle();
};
return IssuableContext;
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
/* global GitLab */
/* global GroupsSelect */
import Pikaday from 'pikaday';
import Autosave from './autosave';
......@@ -8,6 +7,7 @@ import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
import groupsSelect from './groups_select';
(function() {
this.IssuableForm = (function() {
......@@ -22,7 +22,7 @@ import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
this.handleSubmit = this.handleSubmit.bind(this);
new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup();
new UsersSelect();
new GroupsSelect();
groupsSelect();
new ZenMode();
this.titleField = this.form.find("input[name*='[title]']");
this.descriptionField = this.form.find("textarea[name*='[description]']");
......
......@@ -16,15 +16,15 @@
<fieldset>
<label
class="sr-only"
for="issue-title">
for="issuable-title">
Title
</label>
<input
id="issue-title"
id="issuable-title"
class="form-control"
type="text"
placeholder="Issue title"
aria-label="Issue title"
placeholder="Title"
aria-label="Title"
v-model="formState.title"
@keydown.meta.enter="updateIssuable"
@keydown.ctrl.enter="updateIssuable" />
......
......@@ -56,8 +56,6 @@ import './gl_dropdown';
import './gl_field_error';
import './gl_field_errors';
import './gl_form';
import './group_avatar';
import './group_label_subscription';
import './header';
import './importer_status';
import './issuable_index';
......
......@@ -11,8 +11,8 @@ import {
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
/* eslint-disable max-len */
// MergeRequestTabs
......@@ -292,7 +292,7 @@ import initDiscussionTab from './image_diff/init_discussion_tab';
}
this.diffsLoaded = true;
new gl.Diff();
new Diff();
this.scrollToElement('#diffs');
$('.diff-file').each((i, el) => {
......
......@@ -13,7 +13,6 @@ import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import autosize from 'vendor/autosize';
import Dropzone from 'dropzone';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
......@@ -28,7 +27,6 @@ import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from
import imageDiffHelper from './image_diff/helpers/index';
window.autosize = autosize;
window.Dropzone = Dropzone;
function normalizeNewlines(str) {
return str.replace(/\r\n/g, '\n');
......@@ -1283,10 +1281,12 @@ export default class Notes {
* Get data from Form attributes to use for saving/submitting comment.
*/
getFormData($form) {
const content = $form.find('.js-note-text').val();
return {
formData: $form.serialize(),
formContent: _.escape($form.find('.js-note-text').val()),
formContent: _.escape(content),
formAction: $form.attr('action'),
formContentOriginal: content,
};
}
......@@ -1418,7 +1418,7 @@ export default class Notes {
const isMainForm = $form.hasClass('js-main-target-form');
const isDiscussionForm = $form.hasClass('js-discussion-note-form');
const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
const { formData, formContent, formAction } = this.getFormData($form);
const { formData, formContent, formAction, formContentOriginal } = this.getFormData($form);
let noteUniqueId;
let systemNoteUniqueId;
let hasQuickActions = false;
......@@ -1577,7 +1577,7 @@ export default class Notes {
$form = $notesContainer.parent().find('form');
}
$form.find('.js-note-text').val(formContent);
$form.find('.js-note-text').val(formContentOriginal);
this.reenableTargetFormSubmitButton(e);
this.addNoteError($form);
});
......
......@@ -12,6 +12,15 @@
type: Object,
required: true,
},
// Can be rendered in 3 different places, with some visual differences
// Accepts root | child
// `root` -> main view
// `child` -> rendered inside MR or Commit View
viewType: {
type: String,
required: false,
default: 'root',
},
},
components: {
tablePagination,
......@@ -187,7 +196,7 @@
:empty-state-svg-path="emptyStateSvgPath"
/>
<error-state
<error-state
v-if="shouldRenderErrorState"
:error-state-svg-path="errorStateSvgPath"
/>
......@@ -206,6 +215,7 @@
:pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsPath"
:view-type="viewType"
/>
</div>
......
......@@ -21,6 +21,10 @@
type: String,
required: true,
},
viewType: {
type: String,
required: true,
},
},
components: {
pipelinesTableRowComponent,
......@@ -59,6 +63,7 @@
:pipeline="model"
:update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
</div>
</template>
......@@ -29,6 +29,10 @@ export default {
type: String,
required: true,
},
viewType: {
type: String,
required: true,
},
},
components: {
asyncButtonComponent,
......@@ -203,9 +207,13 @@ export default {
displayPipelineActions() {
return this.pipeline.flags.retryable ||
this.pipeline.flags.cancelable ||
this.pipeline.details.manual_actions.length ||
this.pipeline.details.artifacts.length;
this.pipeline.flags.cancelable ||
this.pipeline.details.manual_actions.length ||
this.pipeline.details.artifacts.length;
},
isChildView() {
return this.viewType === 'child';
},
},
};
......@@ -218,7 +226,10 @@ export default {
Status
</div>
<div class="table-mobile-content">
<ci-badge :status="pipelineStatus"/>
<ci-badge
:status="pipelineStatus"
:show-text="!isChildView"
/>
</div>
</div>
......@@ -240,7 +251,9 @@ export default {
:commit-url="commitUrl"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"/>
:author="commitAuthor"
:show-branch="!isChildView"
/>
</div>
</div>
......
......@@ -57,7 +57,7 @@
},
showError(message) {
Flash((errorMessages[message]));
Flash(errorMessages[message]);
},
},
};
......
......@@ -57,7 +57,7 @@
},
showError(message) {
Flash((errorMessages[message]));
Flash(errorMessages[message]);
},
},
};
......
......@@ -29,11 +29,9 @@ export const fetchList = ({ commit }, { repo, page }) => {
});
};
export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath)
.then(res => res.json());
export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath);
export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath)
.then(res => res.json());
export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath);
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
......@@ -38,7 +38,7 @@ export default {
tag: element.name,
revision: element.revision,
shortRevision: element.short_revision,
size: element.size,
size: element.total_size,
layers: element.layers,
location: element.location,
createdAt: element.created_at,
......
<script>
import flash, { hideFlash } from '../../flash';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import eventHub from '../event_hub';
export default {
components: {
loadingIcon,
},
props: {
currentBranch: {
type: String,
required: true,
},
},
data() {
return {
branchName: '',
loading: false,
};
},
computed: {
btnDisabled() {
return this.loading || this.branchName === '';
},
},
methods: {
toggleDropdown() {
this.$dropdown.dropdown('toggle');
},
submitNewBranch() {
// need to query as the element is appended outside of Vue
const flashEl = this.$refs.flashContainer.querySelector('.flash-alert');
this.loading = true;
if (flashEl) {
hideFlash(flashEl, false);
}
eventHub.$emit('createNewBranch', this.branchName);
},
showErrorMessage(message) {
this.loading = false;
flash(message, 'alert', this.$el);
},
createdNewBranch(newBranchName) {
this.loading = false;
this.branchName = '';
if (this.dropdownText) {
this.dropdownText.textContent = newBranchName;
}
},
},
created() {
// Dropdown is outside of Vue instance & is controlled by Bootstrap
this.$dropdown = $('.git-revision-dropdown');
// text element is outside Vue app
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
eventHub.$on('createNewBranchSuccess', this.createdNewBranch);
eventHub.$on('createNewBranchError', this.showErrorMessage);
eventHub.$on('toggleNewBranchDropdown', this.toggleDropdown);
},
destroyed() {
eventHub.$off('createNewBranchSuccess', this.createdNewBranch);
eventHub.$off('toggleNewBranchDropdown', this.toggleDropdown);
eventHub.$off('createNewBranchError', this.showErrorMessage);
},
};
</script>
<template>
<div>
<div
class="flash-container"
ref="flashContainer"
>
</div>
<p>
Create from:
<code>{{ currentBranch }}</code>
</p>
<input
class="form-control js-new-branch-name"
type="text"
placeholder="Name new branch"
v-model="branchName"
@keyup.enter.stop.prevent="submitNewBranch"
/>
<div class="prepend-top-default clearfix">
<button
type="button"
class="btn btn-primary pull-left"
:disabled="btnDisabled"
@click.stop.prevent="submitNewBranch"
>
<loading-icon
v-if="loading"
:inline="true"
/>
<span>Create</span>
</button>
<button
type="button"
class="btn btn-default pull-right"
@click.stop.prevent="toggleDropdown"
>
Cancel
</button>
</div>
</div>
</template>
......@@ -8,7 +8,9 @@ import RepoMixin from '../mixins/repo_mixin';
import PopupDialog from '../../vue_shared/components/popup_dialog.vue';
import Store from '../stores/repo_store';
import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service';
import MonacoLoaderHelper from '../helpers/monaco_loader_helper';
import eventHub from '../event_hub';
export default {
data() {
......@@ -24,12 +26,19 @@ export default {
PopupDialog,
RepoPreview,
},
created() {
eventHub.$on('createNewBranch', this.createNewBranch);
},
mounted() {
Helper.getContent().catch(Helper.loadingError);
},
destroyed() {
eventHub.$off('createNewBranch', this.createNewBranch);
},
methods: {
getCurrentLocation() {
return location.href;
},
toggleDialogOpen(toggle) {
this.dialog.open = toggle;
},
......@@ -38,8 +47,25 @@ export default {
this.toggleDialogOpen(false);
this.dialog.status = status;
},
toggleBlobView: Store.toggleBlobView,
createNewBranch(branch) {
Service.createBranch({
branch,
ref: Store.currentBranch,
}).then((res) => {
const newBranchName = res.data.name;
const newUrl = this.getCurrentLocation().replace(Store.currentBranch, newBranchName);
Store.currentBranch = newBranchName;
history.pushState({ key: Helper.key }, '', newUrl);
eventHub.$emit('createNewBranchSuccess', newBranchName);
eventHub.$emit('toggleNewBranchDropdown');
}).catch((err) => {
eventHub.$emit('createNewBranchError', err.response.data.message);
});
},
},
};
</script>
......
......@@ -28,6 +28,9 @@
marginLeft: `${this.file.level * 16}px`,
};
},
shortId() {
return this.file.id.substr(0, 8);
},
},
methods: {
linkClicked(file) {
......@@ -55,6 +58,17 @@
>
{{ file.name }}
</a>
<template v-if="file.type === 'submodule' && file.id">
@
<span class="commit-sha">
<a
@click.stop
:href="file.tree_url"
>
{{ shortId }}
</a>
</span>
</template>
</td>
<template v-if="!isMini">
......@@ -69,7 +83,10 @@
</td>
<td class="commit-update hidden-xs text-right">
<span :title="tooltipTitle(file.lastCommit.updatedAt)">
<span
v-if="file.lastCommit.updatedAt"
:title="tooltipTitle(file.lastCommit.updatedAt)"
>
{{ timeFormated(file.lastCommit.updatedAt) }}
</span>
</td>
......
......@@ -74,6 +74,10 @@ export default {
if (file.type === 'tree' && file.opened) {
Helper.setDirectoryToClosed(file);
Store.setActiveLine(lineNumber);
} else if (file.type === 'submodule') {
file.loading = true;
gl.utils.visitUrl(file.url);
} else {
const openFile = Helper.getFileFromPath(file.url);
......
......@@ -95,7 +95,7 @@ const RepoHelper = {
return Service.getContent()
.then((response) => {
const data = response.data;
if (response.headers && response.headers['page-title']) data.pageTitle = response.headers['page-title'];
if (response.headers && response.headers['page-title']) data.pageTitle = decodeURI(response.headers['page-title']);
if (response.headers && response.headers['is-root'] && !Store.isInitialRoot) {
Store.isRoot = convertPermissionToBoolean(response.headers['is-root']);
Store.isInitialRoot = Store.isRoot;
......@@ -157,12 +157,14 @@ const RepoHelper = {
},
serializeRepoEntity(type, entity, level = 0) {
const { url, name, icon, last_commit } = entity;
const { id, url, name, icon, last_commit, tree_url } = entity;
return {
id,
type,
name,
url,
tree_url,
level,
icon: `fa-${icon}`,
files: [],
......
......@@ -5,6 +5,7 @@ import Service from './services/repo_service';
import Store from './stores/repo_store';
import Repo from './components/repo.vue';
import RepoEditButton from './components/repo_edit_button.vue';
import newBranchForm from './components/new_branch_form.vue';
import Translate from '../vue_shared/translate';
function initDropdowns() {
......@@ -62,6 +63,26 @@ function initRepoEditButton(el) {
});
}
function initNewBranchForm() {
const el = document.querySelector('.js-new-branch-dropdown');
if (!el) return null;
return new Vue({
el,
components: {
newBranchForm,
},
render(createElement) {
return createElement('new-branch-form', {
props: {
currentBranch: Store.currentBranch,
},
});
},
});
}
function initRepoBundle() {
const repo = document.getElementById('repo');
const editButton = document.querySelector('.editable-mode');
......@@ -73,6 +94,7 @@ function initRepoBundle() {
initRepo(repo);
initRepoEditButton(editButton);
initNewBranchForm();
}
$(initRepoBundle);
......
import axios from 'axios';
import csrf from '../../lib/utils/csrf';
import Store from '../stores/repo_store';
import Api from '../../api';
import Helper from '../helpers/repo_helper';
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
const RepoService = {
url: '',
options: {
......@@ -10,6 +13,7 @@ const RepoService = {
format: 'json',
},
},
createBranchPath: '/api/:version/projects/:id/repository/branches',
richExtensionRegExp: /md/,
getRaw(url) {
......@@ -73,6 +77,12 @@ const RepoService = {
.then(this.commitFlash);
},
createBranch(payload) {
const url = Api.buildUrl(this.createBranchPath)
.replace(':id', Store.projectId);
return axios.post(url, payload);
},
commitFlash(data) {
if (data.short_id && data.stats) {
window.Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
......
......@@ -13,6 +13,7 @@ const RepoStore = {
projectId: '',
projectName: '',
projectUrl: '',
branchUrl: '',
blobRaw: '',
currentBlobView: 'repo-preview',
openedFiles: [],
......
<script>
import ciIcon from './ci_icon.vue';
/**
* Renders CI Badge link with CI icon and status text based on
* API response shared between all places where it is used.
*
* Receives status object containing:
* status: {
* details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url
* group:"running" // used for CSS class
* icon: "icon_status_running" // used to render the icon
* label:"running" // used for potential tooltip
* text:"running" // text rendered
* }
*
* Used in:
* - Pipelines table - first column
* - Jobs table - first column
* - Pipeline show view - header
* - Job show view - header
* - MR widget
*/
import ciIcon from './ci_icon.vue';
import tooltip from '../directives/tooltip';
/**
* Renders CI Badge link with CI icon and status text based on
* API response shared between all places where it is used.
*
* Receives status object containing:
* status: {
* details_path: "/gitlab-org/gitlab-ce/pipelines/8150156" // url
* group:"running" // used for CSS class
* icon: "icon_status_running" // used to render the icon
* label:"running" // used for potential tooltip
* text:"running" // text rendered
* }
*
* Used in:
* - Pipelines table - first column
* - Jobs table - first column
* - Pipeline show view - header
* - Job show view - header
* - MR widget
*/
export default {
props: {
status: {
type: Object,
required: true,
export default {
props: {
status: {
type: Object,
required: true,
},
showText: {
type: Boolean,
required: false,
default: true,
},
},
},
components: {
ciIcon,
},
computed: {
cssClass() {
const className = this.status.group;
components: {
ciIcon,
},
directives: {
tooltip,
},
computed: {
cssClass() {
const className = this.status.group;
return className ? `ci-status ci-${this.status.group}` : 'ci-status';
return className ? `ci-status ci-${className}` : 'ci-status';
},
},
},
};
};
</script>
<template>
<a
:href="status.details_path"
:class="cssClass">
:class="cssClass"
v-tooltip
:title="!showText ? status.text : ''">
<ci-icon :status="status" />
{{status.text}}
<template v-if="showText">
{{status.text}}
</template>
</a>
</template>
......@@ -63,14 +63,17 @@
required: false,
default: () => ({}),
},
showBranch: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
/**
* Used to verify if all the properties needed to render the commit
* ref section were provided.
*
* TODO: Improve this! Use lodash _.has when we have it.
*
* @returns {Boolean}
*/
hasCommitRef() {
......@@ -80,8 +83,6 @@
* Used to verify if all the properties needed to render the commit
* author section were provided.
*
* TODO: Improve this! Use lodash _.has when we have it.
*
* @returns {Boolean}
*/
hasAuthor() {
......@@ -114,31 +115,30 @@
</script>
<template>
<div class="branch-commit">
<div
v-if="hasCommitRef"
class="icon-container hidden-xs">
<i
v-if="tag"
class="fa fa-tag"
aria-hidden="true">
</i>
<i
v-if="!tag"
class="fa fa-code-fork"
aria-hidden="true">
</i>
</div>
<a
v-if="hasCommitRef"
class="ref-name hidden-xs"
:href="commitRef.ref_url"
v-tooltip
data-container="body"
:title="commitRef.name">
{{commitRef.name}}
</a>
<template v-if="hasCommitRef && showBranch">
<div
class="icon-container hidden-xs">
<i
v-if="tag"
class="fa fa-tag"
aria-hidden="true">
</i>
<i
v-if="!tag"
class="fa fa-code-fork"
aria-hidden="true">
</i>
</div>
<a
class="ref-name hidden-xs"
:href="commitRef.ref_url"
v-tooltip
data-container="body"
:title="commitRef.name">
{{commitRef.name}}
</a>
</template>
<div
v-html="commitIconSvg"
class="commit-icon js-commit-icon">
......
......@@ -11,8 +11,6 @@ import Dropzone from 'dropzone';
import 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause';
window.Dropzone = Dropzone;
//
// ### Events
//
......
......@@ -23,6 +23,11 @@
@include webkit-prefix(animation-duration, 2s);
}
&.spin {
transform-origin: center;
animation: spin 4s linear infinite;
}
&.flipOutX,
&.flipOutY,
&.bounceIn,
......@@ -271,3 +276,9 @@ a {
transform: translateX(468px);
}
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
......@@ -330,7 +330,7 @@
position: fixed;
bottom: 0;
padding: 16px;
background-color: $gray-normal;
background-color: $gray-light;
border: 0;
border-top: 2px solid $border-color;
color: $gl-text-color-secondary;
......
......@@ -3,5 +3,5 @@
border-radius: $border-radius-default;
line-height: 16px;
font-weight: $gl-font-weight-normal;
padding: $gl-btn-padding;
padding: 8px;
}
......@@ -361,6 +361,6 @@ class ApplicationController < ActionController::Base
def set_page_title_header
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
response.headers['Page-Title'] = page_title('GitLab').encode('ISO-8859-1')
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
end
......@@ -148,6 +148,17 @@ class GroupsController < Groups::ApplicationController
end
def load_events
params[:sort] ||= 'latest_activity_desc'
options = {}
options[:only_owned] = true if params[:shared] == '0'
options[:only_shared] = true if params[:shared] == '1'
@projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user)
.execute
.includes(:namespace)
.page(params[:page])
@events = EventCollection
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
......
......@@ -48,6 +48,8 @@ class Projects::CommitsController < Projects::ApplicationController
private
def set_commits
render_404 unless request.format == :atom || @repository.blob_at(@commit.id, @path) || @repository.tree(@commit.id, @path).entries.present?
@limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
search = params[:search]
......
......@@ -313,4 +313,8 @@ module ApplicationHelper
def show_new_repo?
cookies["new_repo"] == "true" && body_data_page != 'projects:show'
end
def locale_path
asset_path("locale/#{Gitlab::I18n.locale}/app.js")
end
end
......@@ -36,7 +36,8 @@ module PreferencesHelper
def project_view_choices
[
['Files and Readme (default)', :files],
['Activity', :activity]
['Activity', :activity],
['Readme', :readme]
]
end
......
......@@ -17,7 +17,7 @@ module ProjectsHelper
end
def link_to_member_avatar(author, opts = {})
default_opts = { size: 16 }
default_opts = { size: 16, lazy_load: false }
opts = default_opts.merge(opts)
classes = %W[avatar avatar-inline s#{opts[:size]}]
......@@ -29,8 +29,26 @@ module ProjectsHelper
image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
end
def author_content_tag(author, opts = {})
default_opts = { author_class: 'author', tooltip: false, by_username: false }
opts = default_opts.merge(opts)
has_tooltip = !opts[:by_username] && opts[:tooltip]
username = opts[:by_username] ? author.to_reference : author.name
name_tag_options = { class: [opts[:author_class]] }
if has_tooltip
name_tag_options[:title] = author.to_reference
name_tag_options[:data] = { placement: 'top' }
name_tag_options[:class] << 'has-tooltip'
end
content_tag(:span, sanitize(username), name_tag_options)
end
def link_to_member(project, author, opts = {}, &block)
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false, lazy_load: false }
default_opts = { avatar: true, name: true, title: ":name" }
opts = default_opts.merge(opts)
return "(deleted)" unless author
......@@ -41,12 +59,7 @@ module ProjectsHelper
author_html << link_to_member_avatar(author, opts) if opts[:avatar]
# Build name span tag
if opts[:by_username]
author_html << content_tag(:span, sanitize("@#{author.username}"), class: opts[:author_class]) if opts[:name]
else
tooltip_data = { placement: 'top' }
author_html << content_tag(:span, sanitize(author.name), class: [opts[:author_class], ('has-tooltip' if opts[:tooltip])], title: (author.to_reference if opts[:tooltip]), data: (tooltip_data if opts[:tooltip])) if opts[:name]
end
author_html << author_content_tag(author, opts) if opts[:name]
author_html << capture(&block) if block
......
......@@ -228,7 +228,10 @@ class ApplicationSetting < ActiveRecord::Base
ensure_cache_setup
Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
ApplicationSetting.last.tap do |settings|
# do not cache nils
raise 'missing settings' unless settings
end
end
rescue
# Fall back to an uncached value if there are any problems (e.g. redis down)
......
......@@ -2,7 +2,7 @@ module Ci
class ArtifactBlob
include BlobLike
EXTENTIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json].freeze
EXTENSIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json].freeze
attr_reader :entry
......@@ -36,17 +36,22 @@ module Ci
def external_url(project, job)
return unless external_link?(job)
components = project.full_path_components
components << "-/jobs/#{job.id}/artifacts/file/#{path}"
artifact_path = components[1..-1].join('/')
full_path_parts = project.full_path_components
top_level_group = full_path_parts.shift
"#{pages_config.protocol}://#{components[0]}.#{pages_config.host}/#{artifact_path}"
artifact_path = [
'-', *full_path_parts, '-',
'jobs', job.id,
'artifacts', path
].join('/')
"#{pages_config.protocol}://#{top_level_group}.#{pages_config.host}/#{artifact_path}"
end
def external_link?(job)
pages_config.enabled &&
pages_config.artifacts_server &&
EXTENTIONS_SERVED_BY_PAGES.include?(File.extname(name)) &&
EXTENSIONS_SERVED_BY_PAGES.include?(File.extname(name)) &&
job.project.public?
end
......
......@@ -185,13 +185,8 @@ class User < ActiveRecord::Base
enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos]
# User's Project preference
#
# Note: When adding an option, it MUST go on the end of the hash with a
# number higher than the current max. We cannot move options and/or change
# their numbers.
#
# We skip 0 because this was used by an option that has since been removed.
enum project_view: { activity: 1, files: 2 }
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files]
alias_attribute :private_token, :authentication_token
......
class ContainerTagEntity < Grape::Entity
include RequestAwareEntity
expose :name, :location, :revision, :total_size, :created_at
expose :name, :location, :revision, :short_revision, :total_size, :created_at
expose :destroy_path, if: -> (*) { can_destroy? } do |tag|
project_registry_repository_tag_path(project, tag.repository, tag.name, format: :json)
project_registry_repository_tag_path(project, tag.repository, tag.name)
end
private
......
......@@ -7,7 +7,7 @@ class SubmoduleEntity < Grape::Entity
'archive'
end
expose :project_url do |blob|
expose :url do |blob|
submodule_links(blob, request).first
end
......
......@@ -52,7 +52,7 @@ module Projects
end
def wiki_path
repo_path + '.wiki'
project.wiki.disk_path
end
def trash_repositories!
......
......@@ -16,8 +16,8 @@ module Users
user_cache_key
]
if event.project.forked?
keys << project_cache_key(event.project.forked_from_project)
if forked_from = event.project.forked_from_project
keys << project_cache_key(forked_from)
end
keys.each { |key| set_key(key, event.id) }
......
......@@ -44,4 +44,4 @@
= render "discussions/diff_with_notes", discussion: discussion
- else
.panel.panel-default
= render "discussions/notes", discussion: discussion
= render partial: "discussions/notes", locals: { discussion: discussion, disable_collapse_class: true }
......@@ -4,7 +4,7 @@
%td.notes_line.old
%td.notes_content.parallel.old
.content{ class: ('hide' unless discussions_left.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old'
= render partial: "discussions/notes", collection: discussions_left, as: :discussion, line_type: 'old', locals: { disable_collapse_class: true }
- else
%td.notes_line.old= ("")
%td.notes_content.parallel.old
......@@ -14,7 +14,7 @@
%td.notes_line.new
%td.notes_content.parallel.new
.content{ class: ('hide' unless discussions_right.any?(&:expanded?)) }
= render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new'
= render partial: "discussions/notes", collection: discussions_right, as: :discussion, line_type: 'new', locals: { disable_collapse_class: true }
- else
%td.notes_line.new= ("")
%td.notes_content.parallel.new
......
......@@ -20,13 +20,6 @@
= render 'shared/issuable/search_bar', type: :issues
.row-content-block.second-block
Only issues from the
%strong= @group.name
group are listed here.
- if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
= render 'shared/issues'
- else
= render 'shared/empty_states/issues', project_select_button: true
......@@ -15,11 +15,4 @@
= render 'shared/issuable/search_bar', type: :merge_requests
.row-content-block.second-block
Only merge requests from
%strong= @group.name
group are listed here.
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
= render 'shared/merge_requests'
......@@ -37,7 +37,7 @@
- if content_for?(:library_javascripts)
= yield :library_javascripts
= javascript_include_tag asset_path("locale/#{I18n.locale.to_s || I18n.default_locale.to_s}/app.js") unless I18n.locale == :en
= javascript_include_tag locale_path unless I18n.locale == :en
= webpack_bundle_tag "webpack_runtime"
= webpack_bundle_tag "common"
= webpack_bundle_tag "main"
......
......@@ -288,6 +288,11 @@
= sprite_icon('users')
%span.nav-item-name
Members
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(path: %w[members#show], html_options: { class: "fly-out-top-item" } ) do
= link_to project_settings_members_path(@project) do
%strong.fly-out-top-item-name
#{ _('Members') }
= render 'shared/sidebar_toggle_button'
......
- local_assigns.fetch(:view)
%strong
%span{ data: { defer_to: "#{view.defer_key}-duration" } } ...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } } ...
Gitaly
- if (readme = @repository.readme) && readme.rich_viewer
%article.file-holder.readme-holder{ id: 'readme', class: ("limited-width-container" unless fluid_layout) }
.js-file-title.file-title
= blob_icon readme.mode, readme.name
= link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
%strong
= readme.name
= render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
- else
.row-content-block.second-block.center
%h3.page-title
This project does not have a README yet
- if can?(current_user, :push_code, @project)
%p
A
%code README
file contains information about other files in a repository and is commonly
distributed with computer software, forming part of its documentation.
%p
We recommend you to
= link_to "add a README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link'
file to the repository and GitLab will render it here instead of this message.
......@@ -10,7 +10,7 @@
.col-sm-8.col-sm-offset-4.signin-with-google
- if @authorize_url
= link_to @authorize_url do
= image_tag('auth_buttons/signin_with_google.png')
= image_tag('auth_buttons/signin_with_google.png', width: '191px')
- else
- link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer')
= s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link }
.tree-ref-container
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
- unless show_new_repo?
= render 'projects/tree/old_tree_header'
......
- show_new_branch_form = show_new_repo? && show_create && can?(current_user, :push_code, @project)
- dropdown_toggle_text = @ref || @project.default_branch
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= hidden_field_tag :destination, destination
......@@ -7,8 +8,20 @@
= hidden_field_tag key, value, id: nil
.dropdown
= dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
= dropdown_title _("Switch branch/tag")
= dropdown_filter _("Search branches and tags")
= dropdown_content
= dropdown_loading
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
.dropdown-page-one
= dropdown_title _("Switch branch/tag")
= dropdown_filter _("Search branches and tags")
= dropdown_content
= dropdown_loading
- if show_new_branch_form
= dropdown_footer do
%ul.dropdown-footer-list
%li
%a.dropdown-toggle-page{ href: "#" }
Create new branch
- if show_new_branch_form
.dropdown-page-two
= dropdown_title("Create new branch", options: { back: true })
= dropdown_content do
.js-new-branch-dropdown
This diff is collapsed.
......@@ -14,5 +14,5 @@
= link_to_member(@project, participant, name: false, size: 24, lazy_load: true)
- if participants_extra > 0
.hide-collapsed.participants-more
%a.js-participants-more{ href: "#", data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } }
%button.btn-transparent.btn-blank.js-participants-more{ type: 'button', data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } }
+ #{participants_extra} more
---
title: fix height of rebase and approve buttons
merge_request:
author:
type: fixed
---
title: Decrease ABC threshold to 54.28
merge_request: 14920
author: Maxim Rydkin
type: other
---
title: Add readme only option as project view
merge_request: 14900
author:
type: changed
---
title: Decrease Perceived Complexity threshold to 14
merge_request: 14231
author: Maxim Rydkin
type: other
---
title: Get Project Branch API shows an helpful error message on invalid refname
merge_request: 14884
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Remove help text from group issues page and group merge requests page
merge_request: 14963
author:
type: removed
---
title: Animate auto devops graphic
merge_request:
author:
type: other
---
title: Fix the atom feed for group events
merge_request: 14974
author:
type: fixed
---
title: Only cache last push event for existing projects when pushing to a fork
merge_request: 14989
author:
type: fixed
---
title: Get true failure from evalulate_script by checking for element beforehand
merge_request: 14898
author:
type: fixed
---
title: Fix application setting to cache nil object
merge_request:
author:
type: fixed
---
title: Clarify system_hook triggers in documentation
merge_request: 14957
author: Joe Marty
type: other
---
title: Added submodule support in multi-file editor
merge_request:
author:
type: added
---
title: Renders 404 in commits controller if no commits are found for a given path
merge_request: 14610
author: Guilherme Vieira
type: fixed
---
title: Fix deletion of container registry or images returning an error
merge_request:
author:
type: fixed
---
title: Fix SAML error 500 when no groups are defined for user
merge_request: 14913
author:
type: fixed
---
title: Use title as placeholder instead of issue title for reusability
merge_request:
author:
type: other
---
title: Add Gitaly metrics to the performance bar
merge_request:
author:
type: other
......@@ -16,6 +16,7 @@ Peek.into Peek::Views::Redis
Peek.into Peek::Views::Sidekiq
Peek.into Peek::Views::Rblineprof
Peek.into Peek::Views::GC
Peek.into Peek::Views::Gitaly
# rubocop:disable Style/ClassAndModuleCamelCase
class PEEK_DB_CLIENT
......
......@@ -316,8 +316,13 @@ constraints(ProjectUrlConstrainer.new) do
namespace :registry do
resources :repository, only: [] do
resources :tags, only: [:index, :destroy],
constraints: { id: Gitlab::Regex.container_registry_tag_regex }
# We default to JSON format in the controller to avoid ambiguity.
# `latest.json` could either be a request for a tag named `latest`
# in JSON format, or a request for tag named `latest.json`.
scope format: false do
resources :tags, only: [:index, :destroy],
constraints: { id: Gitlab::Regex.container_registry_tag_regex }
end
end
end
......
......@@ -94,7 +94,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
### Issues and Merge Requests (MRs)
- [Discussions](user/discussions/index.md) Threads, comments, and resolvable discussions in issues, commits, and merge requests.
- [Discussions](user/discussions/index.md): Threads, comments, and resolvable discussions in issues, commits, and merge requests.
- [Issues](user/project/issues/index.md)
- [Project issue Board](user/project/issue_board.md)
- **(EEP)** [Group Issue Boards](user/project/issue_board.md#group-issue-boards)
......@@ -175,7 +175,7 @@ have access to GitLab administration tools and settings.
- [Git LFS configuration](workflow/lfs/lfs_administration.md): Learn how to use LFS under GitLab.
- [GitLab Pages configuration](administration/pages/index.md): Configure GitLab Pages.
- [High Availability](administration/high_availability/README.md): Configure multiple servers for scaling or high availability.
- [User cohorts](user/admin_area/user_cohorts.md) View user activity over time.
- [User cohorts](user/admin_area/user_cohorts.md): View user activity over time.
- [Web terminals](administration/integration/terminal.md): Provide terminal access to environments from within GitLab.
- **(EES/EEP)** [Audit logs and events](administration/audit_events.md): View the changes made within the GitLab server.
- **(EES/EEP)** [Elasticsearch](integration/elasticsearch.md): Enable Elasticsearch which powers GitLab's Advanced Global Search. Useful when you deal with a huge amount of data.
......
......@@ -18,8 +18,7 @@ other than production, the corresponding logfile is shown here.)
It contains a structured log for Rails controller requests received from
GitLab, thanks to [Lograge](https://github.com/roidrage/lograge/). Note that
requests from the API [are not yet logged to this
file](https://gitlab.com/gitlab-org/gitlab-ce/issues/36189).
requests from the API are logged to a separate file in `api_json.log`.
Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example:
......@@ -73,6 +72,27 @@ In this example we can see that server processed an HTTP request with URL
19:34:53 +0200. Also we can see that request was processed by
`Projects::TreeController`.
## `api_json.log`
Introduced in GitLab 10.0, this file lives in
`/var/log/gitlab/gitlab-rails/api_json.log` for Omnibus GitLab packages or in
`/home/git/gitlab/log/api_json.log` for installations from source.
It helps you see requests made directly to the API. For example:
```json
{"time":"2017-10-10T12:30:11.579Z","severity":"INFO","duration":16.84,"db":1.57,"view":15.27,"status":200,"method":"POST","path":"/api/v4/internal/allowed","params":{"action":"git-upload-pack","changes":"_any","gl_repository":null,"project":"root/foobar.git","protocol":"ssh","env":"{}","key_id":"[FILTERED]","secret_token":"[FILTERED]"},"host":"127.0.0.1","ip":"127.0.0.1","ua":"Ruby"}
```
This entry above shows an access to an internal endpoint to check whether an
associated SSH key can download the project in question via a `git fetch` or
`git clone`. In this example, we see:
1. `method`: The HTTP method used to make the request
1. `path`: The relative path of the query
1. `params`: Key-value pairs passed in a query string or HTTP body. Sensitive parameters (e.g. passwords, tokens, etc.) are filtered out.
1. `ua`: The User-Agent of the requester
## `application.log`
This file lives in `/var/log/gitlab/gitlab-rails/application.log` for
......
......@@ -64,7 +64,21 @@ following locations:
## Road to GraphQL
We have changed our plans to move to GraphQL. After reviewing the GraphQL license, anything related to the Facebook BSD plus patent license will not be allowed at GitLab.
Going forward, we will start on moving to
[GraphQL](http://graphql.org/learn/best-practices/) and deprecate the use of
controller-specific endpoints. GraphQL has a number of benefits:
1. We avoid having to maintain two different APIs.
2. Callers of the API can request only what they need.
3. It is versioned by default.
It will co-exist with the current v4 REST API. If we have a v5 API, this should
be a compatibility layer on top of GraphQL.
Although there were some patenting and licensing concerns with GraphQL, these
have been resolved to our satisfaction by the relicensing of the reference
implementations under MIT, and the use of the OWF license for the GraphQL
specification.
## Basic usage
......@@ -442,6 +456,23 @@ Content-Type: application/json
}
```
## Encoding `+` in ISO 8601 dates
If you need to include a `+` in a query parameter, you may need to use `%2B` instead due
a [W3 recommendation](http://www.w3.org/Addressing/URL/4_URI_Recommentations.html) that
causes a `+` to be interpreted as a space. For example, in an ISO 8601 date, you may want to pass
a time in Mountain Standard Time, such as:
```
2017-10-17T23:11:13.000+05:30
```
The correct encoding for the query parameter would be:
```
2017-10-17T23:11:13.000%2B05:30
```
## Clients
There are many unofficial GitLab API Clients for most of the popular
......
......@@ -43,6 +43,7 @@ future GitLab releases.**
| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
| **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
| **CI_DISPOSABLE_ENVIRONMENT** | all | 10.1 | Mark that job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job |
| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
| **CI_ENVIRONMENT_URL** | 9.3 | all | The URL of the environment for this job |
......@@ -74,6 +75,7 @@ future GitLab releases.**
| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
| **CI_SHARED_ENVIRONMENT** | all | 10.1 | Mark that job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
| **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
| **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment |
......
......@@ -95,6 +95,12 @@ be an array or a multi-line string.
`after_script` is used to define the command that will be run after for all
jobs. This has to be an array or a multi-line string.
> **Note:**
The `before_script` and the main `script` are concatenated and run in a single context/container.
The `after_script` is run separately, so depending on the executor, changes done
outside of the working tree might not be visible, e.g. software installed in the
`before_script`.
### stages
`stages` is used to define stages that can be used by jobs.
......
......@@ -71,6 +71,9 @@ Vue specific design patterns and practices.
---
## [Vue Resource](vue_resource.md)
Vue resource specific practices and gotchas.
## [Icons](icons.md)
How we use SVG for our Icons.
......
......@@ -179,6 +179,7 @@ itself, please read this guide: [State Management][state-management]
The Service is a class used only to communicate with the server.
It does not store or manipulate any data. It is not aware of the store or the components.
We use [vue-resource][vue-resource-repo] to communicate with the server.
Refer to [vue resource](vue_resource.md) for more details.
Vue Resource should only be imported in the service file.
......@@ -189,55 +190,6 @@ Vue Resource should only be imported in the service file.
Vue.use(VueResource);
```
#### Vue-resource gotchas
#### Headers
Headers are being parsed into a plain object in an interceptor.
In Vue-resource 1.x `headers` object was changed into an `Headers` object. In order to not change all old code, an interceptor was added.
If you need to write a unit test that takes the headers in consideration, you need to include an interceptor to parse the headers after your test interceptor.
You can see an example in `spec/javascripts/environments/environment_spec.js`:
```javascript
import { headersInterceptor } from './helpers/vue_resource_helper';
beforeEach(() => {
Vue.http.interceptors.push(myInterceptor);
Vue.http.interceptors.push(headersInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, myInterceptor);
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
});
```
#### `.json()`
When making a request to the server, you will most likely need to access the body of the response.
Use `.json()` to convert. Because `.json()` returns a Promise the follwoing structure should be used:
```javascript
service.get('url')
.then(resp => resp.json())
.then((data) => {
this.store.storeData(data);
})
.catch(() => new Flash('Something went wrong'));
```
When using `Poll` (`app/assets/javascripts/lib/utils/poll.js`), the `successCallback` needs to handle `.json()` as a Promise:
```javascript
successCallback: (response) => {
return response.json().then((data) => {
// handle the response
});
}
```
#### CSRF token
We use a Vue Resource interceptor to manage the CSRF token.
`app/assets/javascripts/vue_shared/vue_resource_interceptor.js` holds all our common interceptors.
Note: You don't need to load `app/assets/javascripts/vue_shared/vue_resource_interceptor.js`
since it's already being loaded by `common_vue.js`.
### End Result
The following example shows an application:
......@@ -769,7 +721,6 @@ describe('component', () => {
[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
[one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow
[vue-resource-repo]: https://github.com/pagekit/vue-resource
[vue-resource-interceptor]: https://github.com/pagekit/vue-resource/blob/develop/docs/http.md#interceptors
[vue-test]: https://vuejs.org/v2/guide/unit-testing.html
[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
......
# Vue Resouce
In Vue applications we use [vue-resource][vue-resource-repo] to communicate with the server.
## HTTP Status Codes
### `.json()`
When making a request to the server, you will most likely need to access the body of the response.
Use `.json()` to convert. Because `.json()` returns a Promise the follwoing structure should be used:
```javascript
service.get('url')
.then(resp => resp.json())
.then((data) => {
this.store.storeData(data);
})
.catch(() => new Flash('Something went wrong'));
```
When using `Poll` (`app/assets/javascripts/lib/utils/poll.js`), the `successCallback` needs to handle `.json()` as a Promise:
```javascript
successCallback: (response) => {
return response.json().then((data) => {
// handle the response
});
}
```
### 204
Some endpoints - usually `delete` endpoints - return `204` as the success response.
When handling `204 - No Content` responses, we cannot use `.json()` since it tries to parse the non-existant body content.
When handling `204` responses, do not use `.json`, otherwise the promise will throw an error and will enter the `catch` statement:
```javascript
Vue.http.delete('path')
.then(() => {
// success!
})
.catch(() => {
// handle error
})
```
## Headers
Headers are being parsed into a plain object in an interceptor.
In Vue-resource 1.x `headers` object was changed into an `Headers` object. In order to not change all old code, an interceptor was added.
If you need to write a unit test that takes the headers in consideration, you need to include an interceptor to parse the headers after your test interceptor.
You can see an example in `spec/javascripts/environments/environment_spec.js`:
```javascript
import { headersInterceptor } from './helpers/vue_resource_helper';
beforeEach(() => {
Vue.http.interceptors.push(myInterceptor);
Vue.http.interceptors.push(headersInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, myInterceptor);
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
});
```
## CSRF token
We use a Vue Resource interceptor to manage the CSRF token.
`app/assets/javascripts/vue_shared/vue_resource_interceptor.js` holds all our common interceptors.
Note: You don't need to load `app/assets/javascripts/vue_shared/vue_resource_interceptor.js`
since it's already being loaded by `common_vue.js`.
[vue-resource-repo]: https://github.com/pagekit/vue-resource
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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