Commit 25cc1dde authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 46971-extra-whitespace

* master: (32 commits)
  Update CHANGELOG.md for 10.8.3
  Update "col-xs" usage to bootstrap4
  Add missing usage_ping_enabled to API settings
  Make Repository#blob_data_at a public method
  Fix UI broken in line profiling modal due to Bootstrap 4
  Only preload member records for the relevant projects/groups/user in projects API
  Fix   after sign-in with Google button
  Fix a wrong link reference in docs
  Simplify spec: `2.times.each` -> `2.times`
  Replace grape-route-helpers with our own grape-path-helpers
  Add archived param to the json response
  Consider we might have prefix for stable branches
  Convert spec/javascripts/.eslintrc to yml format as well
  Remove version lock on awesome_print dependency
  Fix import project by url layout
  Fix missing timeout value in object storage pre-authorization call
  fix last lints
  disable no-multi-assign
  fix `spaced-comment`
  remove unused createComponentWithMixin from vue spec helpers
  ...
parents bcf30db8 37a7b590
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"airbnb-base",
"plugin:vue/recommended"
],
"globals": {
"__webpack_public_path__": true,
"gl": false,
"gon": false,
"localStorage": false
},
"parserOptions": {
"parser": "babel-eslint"
},
"plugins": [
"filenames",
"import",
"html",
"promise"
],
"settings": {
"html/html-extensions": [".html", ".html.raw"],
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
}
}
},
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+$"],
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
"no-mixed-operators": 0,
"space-before-function-paren": 0,
"curly": 0,
"arrow-parens": 0,
"vue/html-self-closing": [
"error",
{
"html": {
"void": "always",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}
]
}
}
---
env:
browser: true
es6: true
extends:
- airbnb-base
- plugin:vue/recommended
globals:
__webpack_public_path__: true
gl: false
gon: false
localStorage: false
parserOptions:
parser: babel-eslint
plugins:
- filenames
- import
- html
- promise
settings:
html/html-extensions:
- ".html"
- ".html.raw"
import/resolver:
webpack:
config: "./config/webpack.config.js"
rules:
filenames/match-regex:
- error
- "^[a-z0-9_]+$"
import/no-commonjs: error
no-multiple-empty-lines:
- error
- max: 1
promise/catch-or-return: error
no-underscore-dangle:
- error
- allow:
- __
- _links
no-mixed-operators: off
vue/html-self-closing:
- error
- html:
void: always
normal: never
component: always
svg: always
math: always
## Conflicting rules with prettier:
space-before-function-paren: off
curly: off
arrow-parens: off
function-paren-newline: off
object-curly-newline: off
padded-blocks: off
# Disabled for now, to make the eslint 3 -> eslint 4 update smoother
## Indent rule. We are using the old for now: https://eslint.org/docs/user-guide/migrating-to-4.0.0#indent-rewrite
indent: off
indent-legacy:
- error
- 2
- SwitchCase: 1
VariableDeclarator: 1
outerIIFEBody: 1
FunctionDeclaration:
parameters: 1
body: 1
FunctionExpression:
parameters: 1
body: 1
## Destructuring: https://eslint.org/docs/rules/prefer-destructuring
prefer-destructuring: off
## no-restricted-globals: https://eslint.org/docs/rules/no-restricted-globals
no-restricted-globals: off
## no-multi-assign: https://eslint.org/docs/rules/no-multi-assign
no-multi-assign: off
......@@ -591,7 +591,7 @@ ee_compat_check:
except:
- master
- tags
- /^[\d-]+-stable(-ee)?/
- /[\d-]+-stable(-ee)?/
- /^security-/
- branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee
......
......@@ -2,6 +2,20 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.8.3 (2018-05-30)
### Fixed (4 changes)
- Replace Gitlab::REVISION with Gitlab.revision and handle installations without a .git directory. !19125
- Fix encoding of branch names on compare and new merge request page. !19143
- Fix remote mirror database inconsistencies when upgrading from EE to CE. !19196
- Fix local storage not being cleared after creating a new issue.
### Performance (1 change)
- Memoize Gitlab::Database.version.
## 10.8.2 (2018-05-28)
### Security (3 changes)
......
......@@ -28,7 +28,7 @@ gem 'mysql2', '~> 0.4.10', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.27'
gem 'grape-route-helpers', '~> 2.1.0'
gem 'grape-path-helpers', '~> 1.0'
gem 'faraday', '~> 0.12'
......@@ -219,7 +219,7 @@ gem 'asana', '~> 0.6.0'
gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration
gem 'kubeclient', '~> 3.0'
gem 'kubeclient', '~> 3.1.0'
# Sanitize user input
gem 'sanitize', '~> 2.0'
......@@ -320,7 +320,7 @@ group :development, :test do
gem 'pry-byebug', '~> 3.4.1', platform: :mri
gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.8.0', require: false
gem 'awesome_print', require: false
gem 'fuubar', '~> 2.2.0'
gem 'database_cleaner', '~> 1.5.0'
......
......@@ -168,7 +168,7 @@ GEM
diff-lcs (1.3)
diffy (3.1.0)
docile (1.1.5)
domain_name (0.5.20170404)
domain_name (0.5.20180417)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.3.2)
railties (>= 4.2)
......@@ -348,7 +348,7 @@ GEM
signet (~> 0.7)
gpgme (2.0.13)
mini_portile2 (~> 2.1)
grape (1.0.2)
grape (1.0.3)
activesupport
builder
mustermann-grape (~> 1.0.0)
......@@ -358,10 +358,10 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
grape-route-helpers (2.1.0)
activesupport
grape (>= 0.16.0)
rake
grape-path-helpers (1.0.1)
activesupport (~> 4)
grape (~> 1.0)
rake (~> 12)
grape_logging (1.7.0)
grape
grpc (1.11.0)
......@@ -446,9 +446,9 @@ GEM
knapsack (1.16.0)
rake
timecop (>= 0.1.0)
kubeclient (3.0.0)
kubeclient (3.1.0)
http (~> 2.2.2)
recursive-open-struct (~> 1.0.4)
recursive-open-struct (~> 1.0, >= 1.0.4)
rest-client (~> 2.0)
launchy (2.4.3)
addressable (~> 2.3)
......@@ -698,7 +698,7 @@ GEM
re2 (1.1.1)
recaptcha (3.0.0)
json
recursive-open-struct (1.0.5)
recursive-open-struct (1.1.0)
redcarpet (3.4.0)
redis (3.3.5)
redis-actionpack (5.0.2)
......@@ -977,7 +977,7 @@ DEPENDENCIES
asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0)
awesome_print (~> 1.8.0)
awesome_print
babosa (~> 1.0.2)
base32 (~> 0.3.0)
batch-loader (~> 1.2.1)
......@@ -1049,7 +1049,7 @@ DEPENDENCIES
gpgme
grape (~> 1.0)
grape-entity (~> 0.7.1)
grape-route-helpers (~> 2.1.0)
grape-path-helpers (~> 1.0)
grape_logging (~> 1.7)
grpc (~> 1.11.0)
haml_lint (~> 0.26.0)
......@@ -1067,7 +1067,7 @@ DEPENDENCIES
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.16)
kubeclient (~> 3.0)
kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 3.1)
licensee (~> 8.9)
......
......@@ -41,10 +41,10 @@ gl.issueBoards.ModalEmptyState = Vue.extend({
template: `
<section class="empty-state">
<div class="row">
<div class="col-xs-12 col-sm-6 order-sm-last">
<div class="col-12 col-md-6 order-md-last">
<aside class="svg-content"><img :src="emptyStateSvg"/></aside>
</div>
<div class="col-xs-12 col-sm-6 order-sm-first">
<div class="col-12 col-md-6 order-md-first">
<div class="text-content">
<h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p>
......
/* global ListIssue */
import Vue from 'vue';
import bp from '../../../breakpoints';
import ModalStore from '../../stores/modal_store';
......@@ -56,8 +54,11 @@ gl.issueBoards.ModalList = Vue.extend({
scrollHandler() {
const currentPage = Math.floor(this.issues.length / this.perPage);
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
&& currentPage === this.page) {
if (
this.scrollTop() > this.scrollHeight() - 100 &&
!this.loadingNewPage &&
currentPage === this.page
) {
this.loadingNewPage = true;
this.page += 1;
}
......
<script>
/* global ListIssue */
import $ from 'jquery';
import _ from 'underscore';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import Api from '../../api';
import $ from 'jquery';
import _ from 'underscore';
import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import Api from '../../api';
export default {
export default {
name: 'BoardProjectSelect',
components: {
loadingIcon,
......@@ -48,7 +46,7 @@
selectable: true,
data: (term, callback) => {
this.loading = true;
return Api.groupProjects(this.groupId, term, (projects) => {
return Api.groupProjects(this.groupId, term, projects => {
this.loading = false;
callback(projects);
});
......@@ -65,7 +63,7 @@
text: project => project.name,
});
},
};
};
</script>
<template>
......
/* global monaco */
import Disposable from './disposable';
import eventHub from '../../eventhub';
......
......@@ -84,11 +84,11 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive
});
};
export const setFileMrChange = ({ state, commit }, { file, mrChange }) => {
export const setFileMrChange = ({ commit }, { file, mrChange }) => {
commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange });
};
export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => {
export const getRawFileData = ({ state, commit }, { path, baseSha }) => {
const file = state.entries[path];
return new Promise((resolve, reject) => {
service
......@@ -156,7 +156,7 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn
}
};
export const setFileViewMode = ({ state, commit }, { file, viewMode }) => {
export const setFileViewMode = ({ commit }, { file, viewMode }) => {
commit(types.SET_FILE_VIEWMODE, { file, viewMode });
};
......
......@@ -3,7 +3,7 @@ import service from '../../services';
import * as types from '../mutation_types';
export const getMergeRequestData = (
{ commit, state, dispatch },
{ commit, state },
{ projectId, mergeRequestId, force = false } = {},
) =>
new Promise((resolve, reject) => {
......@@ -32,7 +32,7 @@ export const getMergeRequestData = (
});
export const getMergeRequestChanges = (
{ commit, state, dispatch },
{ commit, state },
{ projectId, mergeRequestId, force = false } = {},
) =>
new Promise((resolve, reject) => {
......@@ -58,7 +58,7 @@ export const getMergeRequestChanges = (
});
export const getMergeRequestVersions = (
{ commit, state, dispatch },
{ commit, state },
{ projectId, mergeRequestId, force = false } = {},
) =>
new Promise((resolve, reject) => {
......
......@@ -7,10 +7,7 @@ import Poll from '../../../lib/utils/poll';
let eTagPoll;
export const getProjectData = (
{ commit, state, dispatch },
{ namespace, projectId, force = false } = {},
) =>
export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) =>
new Promise((resolve, reject) => {
if (!state.projects[`${namespace}/${projectId}`] || force) {
commit(types.TOGGLE_LOADING, { entry: state });
......@@ -40,10 +37,7 @@ export const getProjectData = (
}
});
export const getBranchData = (
{ commit, state, dispatch },
{ projectId, branchId, force = false } = {},
) =>
export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) =>
new Promise((resolve, reject) => {
if (
typeof state.projects[`${projectId}`] === 'undefined' ||
......@@ -78,7 +72,7 @@ export const getBranchData = (
}
});
export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId, branchId } = {}) =>
export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {}) =>
service
.getBranchData(projectId, branchId)
.then(({ data }) => {
......@@ -92,7 +86,7 @@ export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId,
flash(__('Error loading last commit.'), 'alert', document, null, false, true);
});
export const pollSuccessCallBack = ({ commit, state, dispatch }, { data }) => {
export const pollSuccessCallBack = ({ commit, state }, { data }) => {
if (data.pipelines && data.pipelines.length) {
const lastCommitHash =
state.projects[state.currentProjectId].branches[state.currentBranchId].commit.id;
......
......@@ -5,7 +5,7 @@ import * as types from '../mutation_types';
import { findEntry } from '../utils';
import FilesDecoratorWorker from '../workers/files_decorator_worker';
export const toggleTreeOpen = ({ commit, dispatch }, path) => {
export const toggleTreeOpen = ({ commit }, path) => {
commit(types.TOGGLE_TREE_OPEN, path);
};
......@@ -23,7 +23,7 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
}
};
export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
export const getLastCommitData = ({ state, commit, dispatch }, tree = state) => {
if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
service
......@@ -49,7 +49,7 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s
.catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
};
export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) =>
export const getFiles = ({ state, commit }, { projectId, branchId } = {}) =>
new Promise((resolve, reject) => {
if (!state.trees[`${projectId}/${branchId}`]) {
const selectedProject = state.projects[projectId];
......
......@@ -31,9 +31,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => {
const currentProject = rootState.projects[rootState.currentProjectId];
const commitStats = data.stats
? sprintf(__('with %{additions} additions, %{deletions} deletions.'), {
additions: data.stats.additions, // eslint-disable-line indent
deletions: data.stats.deletions, // eslint-disable-line indent
}) // eslint-disable-line indent
additions: data.stats.additions, // eslint-disable-line indent-legacy
deletions: data.stats.deletions, // eslint-disable-line indent-legacy
}) // eslint-disable-line indent-legacy
: '';
const commitMsg = sprintf(
__('Your changes have been committed. Commit %{commitId} %{commitStats}'),
......@@ -74,10 +74,7 @@ export const checkCommitStatus = ({ rootState }) =>
),
);
export const updateFilesAfterCommit = (
{ commit, dispatch, state, rootState, rootGetters },
{ data },
) => {
export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }) => {
const selectedProject = rootState.projects[rootState.currentProjectId];
const lastCommit = {
commit_path: `${selectedProject.web_url}/commit/${data.id}`,
......
......@@ -84,7 +84,7 @@ export default class Job {
If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not
then we use a polyfill
**/
*/
if (this.$topBar.css('position') !== 'static') return;
StickyFill.add(this.$topBar);
......
/* global Build */
import Visibility from 'visibilityjs';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
......@@ -50,7 +48,8 @@ export default class JobMediator {
}
getJob() {
return this.service.getJob()
return this.service
.getJob()
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
}
......
......@@ -9,7 +9,7 @@ delete window.translations;
Translates `text`
@param text The text to be translated
@returns {String} The translated text
**/
*/
const gettext = locale.gettext.bind(locale);
/**
......@@ -21,7 +21,7 @@ const gettext = locale.gettext.bind(locale);
@param pluralText Plural text to translate (eg. '%d days')
@param count Number to decide which translation to use (eg. 2)
@returns {String} Translated text with the number replaced (eg. '2 days')
**/
*/
const ngettext = (text, pluralText, count) => {
const translated = locale.ngettext(text, pluralText, count).replace(/%d/g, count).split('|');
......@@ -38,7 +38,7 @@ const ngettext = (text, pluralText, count) => {
(eg. 'Context')
@param key Is the dynamic variable you want to be translated
@returns {String} Translated context based text
**/
*/
const pgettext = (keyOrContext, key) => {
const normalizedKey = key ? `${keyOrContext}|${key}` : keyOrContext;
const translated = gettext(normalizedKey).split('|');
......
......@@ -10,7 +10,7 @@ import _ from 'underscore';
@see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf
@see https://gitlab.com/gitlab-org/gitlab-ce/issues/37992
**/
*/
export default (input, parameters, escapeParameters = true) => {
let output = input;
......
......@@ -427,7 +427,7 @@ export default class MergeRequestTabs {
If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not
then we default back to Bootstraps affix
**/
*/
if ($tabs.css('position') !== 'static') return;
const $diffTabs = $('#diff-notes-app');
......
......@@ -12,20 +12,13 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
let eTagPoll;
export const setNotesData = ({ commit }, data) =>
commit(types.SET_NOTES_DATA, data);
export const setNoteableData = ({ commit }, data) =>
commit(types.SET_NOTEABLE_DATA, data);
export const setUserData = ({ commit }, data) =>
commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) =>
commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) =>
commit(types.SET_INITIAL_NOTES, data);
export const setTargetNoteHash = ({ commit }, data) =>
commit(types.SET_TARGET_NOTE_HASH, data);
export const toggleDiscussion = ({ commit }, data) =>
commit(types.TOGGLE_DISCUSSION, data);
export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data);
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data);
export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data);
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
export const fetchNotes = ({ commit }, path) =>
service
......@@ -69,20 +62,14 @@ export const createNewNote = ({ commit }, { endpoint, data }) =>
return res;
});
export const removePlaceholderNotes = ({ commit }) =>
commit(types.REMOVE_PLACEHOLDER_NOTES);
export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES);
export const toggleResolveNote = (
{ commit },
{ endpoint, isResolved, discussion },
) =>
export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) =>
service
.toggleResolveNote(endpoint, isResolved)
.then(res => res.json())
.then(res => {
const mutationType = discussion
? types.UPDATE_DISCUSSION
: types.UPDATE_NOTE;
const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
commit(mutationType, res);
});
......@@ -114,7 +101,7 @@ export const reopenIssue = ({ commit, dispatch, state }) => {
export const toggleStateButtonLoading = ({ commit }, value) =>
commit(types.TOGGLE_STATE_BUTTON_LOADING, value);
export const emitStateChangedEvent = ({ commit, getters }, data) => {
export const emitStateChangedEvent = ({ getters }, data) => {
const event = new CustomEvent('issuable_vue_app:change', {
detail: {
data,
......@@ -179,10 +166,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
loadAwardsHandler()
.then(awardsHandler => {
awardsHandler.addAwardToEmojiBar(
votesBlock,
commandsChanges.emoji_award,
);
awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award);
awardsHandler.scrollToAwards();
})
.catch(() => {
......@@ -194,10 +178,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
});
}
if (
commandsChanges.spend_time != null ||
commandsChanges.time_estimate != null
) {
if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) {
sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res);
}
}
......@@ -218,14 +199,8 @@ const pollSuccessCallBack = (resp, commit, state, getters) => {
resp.notes.forEach(note => {
if (notesById[note.id]) {
commit(types.UPDATE_NOTE, note);
} else if (
note.type === constants.DISCUSSION_NOTE ||
note.type === constants.DIFF_NOTE
) {
const discussion = utils.findNoteObjectById(
state.notes,
note.discussion_id,
);
} else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
const discussion = utils.findNoteObjectById(state.notes, note.discussion_id);
if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
......@@ -249,11 +224,8 @@ export const poll = ({ commit, state, getters }) => {
method: 'poll',
data: state,
successCallback: resp =>
resp
.json()
.then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () =>
Flash('Something went wrong while fetching latest comments.'),
resp.json().then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
});
if (!Visibility.hidden()) {
......@@ -292,14 +264,11 @@ export const fetchData = ({ commit, state, getters }) => {
.catch(() => Flash('Something went wrong while fetching latest comments.'));
};
export const toggleAward = (
{ commit, state, getters, dispatch },
{ awardName, noteId },
) => {
export const toggleAward = ({ commit, getters }, { awardName, noteId }) => {
commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] });
};
export const toggleAwardRequest = ({ commit, getters, dispatch }, data) => {
export const toggleAwardRequest = ({ dispatch }, data) => {
const { endpoint, awardName } = data;
return service
......
......@@ -5,7 +5,7 @@ import $ from 'jquery';
*
* Toggling this checkbox adds/removes a `remember_me` parameter to the
* login buttons' href, which is passed on to the omniauth callback.
**/
*/
export default class OAuthRememberMe {
constructor(opts = {}) {
......
......@@ -7,9 +7,10 @@ Vue.use(VueResource);
export const fetchRepos = ({ commit, state }) => {
commit(types.TOGGLE_MAIN_LOADING);
return Vue.http.get(state.endpoint)
return Vue.http
.get(state.endpoint)
.then(res => res.json())
.then((response) => {
.then(response => {
commit(types.TOGGLE_MAIN_LOADING);
commit(types.SET_REPOS_LIST, response);
});
......@@ -18,19 +19,20 @@ export const fetchRepos = ({ commit, state }) => {
export const fetchList = ({ commit }, { repo, page }) => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
return Vue.http.get(repo.tagsPath, { params: { page } })
.then((response) => {
return Vue.http.get(repo.tagsPath, { params: { page } }).then(response => {
const headers = response.headers;
return response.json().then((resp) => {
return response.json().then(resp => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
commit(types.SET_REGISTRY_LIST, { repo, resp, headers });
});
});
};
// eslint-disable-next-line no-unused-vars
export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath);
// eslint-disable-next-line no-unused-vars
export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath);
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
......
......@@ -13,7 +13,7 @@ export default (Vue) => {
@param text The text to be translated
@returns {String} The translated text
**/
*/
__,
/**
Translate the text with a number
......@@ -24,7 +24,7 @@ export default (Vue) => {
@param pluralText Plural text to translate (eg. '%d days')
@param count Number to decide which translation to use (eg. 2)
@returns {String} Translated text with the number replaced (eg. '2 days')
**/
*/
n__,
/**
Translate context based text
......@@ -36,7 +36,7 @@ export default (Vue) => {
(eg. 'Context')
@param key Is the dynamic variable you want to be translated
@returns {String} Translated context based text
**/
*/
s__,
sprintf,
},
......
......@@ -74,12 +74,6 @@ body.modal-open {
}
}
@include media-breakpoint-up(lg) {
.modal-full {
width: 98%;
}
}
.modal {
background-color: $black-transparent;
z-index: 2100;
......
module Groups
class SharedProjectsController < Groups::ApplicationController
respond_to :json
before_action :group
skip_cross_project_access_check :index
def index
shared_projects = GroupProjectsFinder.new(
group: group,
current_user: current_user,
params: finder_params,
options: { only_shared: true }
).execute
serializer = GroupChildSerializer.new(current_user: current_user)
.with_pagination(request, response)
render json: serializer.represent(shared_projects)
end
private
def finder_params
@finder_params ||= begin
# Make the `search` param consistent for the frontend,
# which will be using `filter`.
params[:search] ||= params[:filter] if params[:filter]
params.permit(:sort, :search)
end
end
end
end
......@@ -957,6 +957,14 @@ class Repository
remote_branch: merge_request.target_branch)
end
def blob_data_at(sha, path)
blob = blob_at(sha, path)
return unless blob
blob.load_all_data!
blob.data
end
def squash(user, merge_request)
raw.squash(user, merge_request.id, branch: merge_request.target_branch,
start_sha: merge_request.diff_start_sha,
......@@ -979,14 +987,6 @@ class Repository
::Commit.new(commit, @project) if commit
end
def blob_data_at(sha, path)
blob = blob_at(sha, path)
return unless blob
blob.load_all_data!
blob.data
end
def cache
@cache ||= Gitlab::RepositoryCache.new(self)
end
......
......@@ -206,10 +206,11 @@ class Service < ActiveRecord::Base
args.each do |arg|
class_eval %{
def #{arg}?
# '!!' is used because nil or empty string is converted to nil
if Gitlab.rails5?
!ActiveModel::Type::Boolean::FALSE_VALUES.include?(#{arg})
!!ActiveRecord::Type::Boolean.new.cast(#{arg})
else
ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
!!ActiveRecord::Type::Boolean.new.type_cast_from_database(#{arg})
end
end
}
......
......@@ -31,7 +31,7 @@ class GroupChildEntity < Grape::Entity
end
# Project only attributes
expose :star_count,
expose :star_count, :archived,
if: lambda { |_instance, _options| project? }
# Group only attributes
......
......@@ -11,6 +11,7 @@ module ObjectStorage
ObjectStorageUnavailable = Class.new(StandardError)
DIRECT_UPLOAD_TIMEOUT = 4.hours
DIRECT_UPLOAD_EXPIRE_OFFSET = 15.minutes
TMP_UPLOAD_PATH = 'tmp/uploads'.freeze
module Store
......@@ -174,11 +175,12 @@ module ObjectStorage
id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-')
upload_path = File.join(TMP_UPLOAD_PATH, id)
connection = ::Fog::Storage.new(self.object_store_credentials)
expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT
expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT + DIRECT_UPLOAD_EXPIRE_OFFSET
options = { 'Content-Type' => 'application/octet-stream' }
{
ID: id,
Timeout: DIRECT_UPLOAD_TIMEOUT,
GetURL: connection.get_object_url(remote_store_path, upload_path, expire_at),
DeleteURL: connection.delete_object_url(remote_store_path, upload_path, expire_at),
StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options)
......
- active_tab = local_assigns.fetch(:active_tab, 'blank')
- f = local_assigns.fetch(:f)
.project-import.row
.col-lg-12
.project-import
.form-group.import-btn-container.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from
......@@ -44,7 +43,6 @@
- if git_import_enabled?
%button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } }
= icon('git', text: 'Repo by URL')
.col-lg-12
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
%hr
= render "shared/import_form", f: f
......
......@@ -35,11 +35,10 @@
%span (optional)
= f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250
.form-group.visibility-level-setting
= f.label :visibility_level, class: 'label-light' do
= f.label :visibility_level, class: 'label-light' do
Visibility Level
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
......@@ -12,8 +12,7 @@
.row
.col-sm-8.offset-sm-4.signin-with-google
- if @authorize_url
= link_to @authorize_url do
= image_tag('auth_buttons/signin_with_google.png', width: '191px')
= link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px'), @authorize_url)
= _('or')
= link_to('create a new Google account', 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral', target: '_blank', rel: 'noopener noreferrer')
- else
......
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
.form-group.row.import-url-data
.form-group.import-url-data
= f.label :import_url, class: 'label-light' do
%span
= _('Git repository URL')
= f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true
.card.prepend-top-20
.info-well.prepend-top-20
.well-segment
%ul
%li
= _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe
......
- with_label = local_assigns.fetch(:with_label, true)
.form-group.row.visibility-level-setting
.form-group.visibility-level-setting
- if with_label
= f.label :visibility_level, class: 'col-form-label col-sm-2' do
Visibility Level
......
.row.empty-state.text-center
.col-xs-12
.col-12
.svg-130.prepend-top-default
= image_tag 'illustrations/issue-dashboard_results-without-filter.svg'
.col-xs-12
.col-12
.text-content
%h4
= _("Please select at least one filter to see results")
.row.empty-state
.col-xs-12
.col-12
.svg-content
= image_tag image_path
.col-xs-12
.col-12
.text-content.text-center
= yield
---
title: Replace Gitlab::REVISION with Gitlab.revision and handle installations without
a .git directory
merge_request: 19125
author:
type: fixed
---
title: Fix UI broken in line profiling modal due to Bootstrap 4
merge_request: 19253
author: Takuya Noguchi
type: other
---
title: Updates the version of kubeclient from 3.0 to 3.1.0
merge_request: 19199
author:
type: other
---
title: Only preload member records for the relevant projects/groups/user in projects
API
merge_request:
author:
type: performance
---
title: Fix remote mirror database inconsistencies when upgrading from EE to CE
merge_request: 19196
author:
type: fixed
---
title: Missing timeout value in object storage pre-authorization
merge_request: 19201
author:
type: fixed
---
title: Fix local storage not being cleared after creating a new issue
title: Fix &nbsp; after sign-in with Google button
merge_request:
author:
type: fixed
---
title: Memoize Gitlab::Database.version
title: Replace grape-route-helpers with our own grape-path-helpers
merge_request:
author:
type: performance
---
title: Fix encoding of branch names on compare and new merge request page
merge_request: 19143
author:
type: fixed
if defined?(GrapeRouteHelpers)
module GrapeRouteHelpers
module AllRoutes
# Bringing in PR https://github.com/reprah/grape-route-helpers/pull/21 due to abandonment.
#
# Without the following fix, when two helper methods are the same, but have different arguments
# (for example: api_v1_cats_owners_path(id: 1) vs api_v1_cats_owners_path(id: 1, owner_id: 2))
# if the helper method with the least number of arguments is defined first (because the route was defined first)
# then it will shadow the longer route.
#
# The fix is to sort descending by amount of arguments
def decorated_routes
@decorated_routes ||= all_routes
.map { |r| DecoratedRoute.new(r) }
.sort_by { |r| -r.dynamic_path_segments.count }
end
end
class DecoratedRoute
# GrapeRouteHelpers gem tries to parse the versions
# from a string, not supporting Grape `version` array definition.
#
# Without the following fix, we get this on route helpers generation:
#
# => undefined method `scan' for ["v3", "v4"]
#
# 2.0.0 implementation of this method:
#
# ```
# def route_versions
# version_pattern = /[^\[",\]\s]+/
# if route_version
# route_version.scan(version_pattern)
# else
# [nil]
# end
# end
# ```
def route_versions
return [nil] if route_version.nil? || route_version.empty?
if route_version.is_a?(String)
version_pattern = /[^\[",\]\s]+/
route_version.scan(version_pattern)
else
route_version
end
end
end
end
end
......@@ -30,6 +30,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :variables, only: [:show, :update]
resources :children, only: [:index]
resources :shared_projects, only: [:index]
resources :labels, except: [:show] do
post :toggle_subscription, on: :member
......
......@@ -80,7 +80,7 @@ STDERR takes precedence over STDOUT.
![Custom message from custom Git hook](img/custom_hooks_error_msg.png)
[CI]: ../ci/readme.md
[CI]: ../ci/README.md
[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks
[webhooks]: ../user/project/integrations/webhooks.md
[5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073
......
......@@ -832,8 +832,8 @@ module API
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
if options.key?(:project_members)
(options[:project_members] || []).find { |member| member.source_id == project.id }
if options[:project_members]
options[:project_members].find { |member| member.source_id == project.id }
else
project.project_member(options[:current_user])
end
......@@ -841,8 +841,8 @@ module API
expose :group_access, using: Entities::GroupAccess do |project, options|
if project.group
if options.key?(:group_members)
(options[:group_members] || []).find { |member| member.source_id == project.namespace_id }
if options[:group_members]
options[:group_members].find { |member| member.source_id == project.namespace_id }
else
project.group.group_member(options[:current_user])
end
......@@ -853,13 +853,24 @@ module API
def self.preload_relation(projects_relation, options = {})
relation = super(projects_relation, options)
unless options.key?(:group_members)
relation = relation.preload(group: [group_members: [:source, user: [notification_settings: :source]]])
# MySQL doesn't support LIMIT inside an IN subquery
if Gitlab::Database.mysql?
project_ids = relation.pluck('projects.id')
namespace_ids = relation.pluck(:namespace_id)
else
project_ids = relation.select('projects.id')
namespace_ids = relation.select(:namespace_id)
end
unless options.key?(:project_members)
relation = relation.preload(project_members: [:source, user: [notification_settings: :source]])
end
options[:project_members] = options[:current_user]
.project_members
.where(source_id: project_ids)
.preload(:source, user: [notification_settings: :source])
options[:group_members] = options[:current_user]
.group_members
.where(source_id: namespace_ids)
.preload(:source, user: [notification_settings: :source])
relation
end
......
module API
module Helpers
module RelatedResourcesHelpers
include GrapeRouteHelpers::NamedRouteMatcher
include GrapePathHelpers::NamedRouteMatcher
def issues_available?(project, options)
available?(:issues, project, options[:current_user])
......
......@@ -58,16 +58,9 @@ module API
projects = paginate(projects)
projects, options = with_custom_attributes(projects, options)
if current_user
project_members = current_user.project_members.preload(:source, user: [notification_settings: :source])
group_members = current_user.group_members.preload(:source, user: [notification_settings: :source])
end
options = options.reverse_merge(
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
statistics: params[:statistics],
project_members: project_members,
group_members: group_members,
current_user: current_user
)
options[:with] = Entities::BasicProjectDetails if params[:simple]
......
......@@ -129,6 +129,7 @@ module API
optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.'
optional :gitaly_timeout_fast, type: Integer, desc: 'Gitaly fast operation timeout, in seconds. Set to 0 to disable timeouts.'
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
......
......@@ -41,10 +41,10 @@ module Peek
]
end.sort_by{ |a,b,c,d,e,f| -f }
output = "<div class='modal-dialog modal-full'><div class='modal-content'>"
output = "<div class='modal-dialog modal-lg'><div class='modal-content'>"
output << "<div class='modal-header'>"
output << "<button class='close btn btn-link btn-sm' type='button' data-dismiss='modal'>X</button>"
output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>"
output << "<button class='close' type='button' data-dismiss='modal' aria-label='close'><span aria-hidden='true'>&times;</span></button>"
output << "</div>"
output << "<div class='modal-body'>"
......
require 'spec_helper'
describe Groups::SharedProjectsController do
def get_shared_projects(params = {})
get :index, params.reverse_merge(format: :json, group_id: group.full_path)
end
def share_project(project)
Projects::GroupLinks::CreateService.new(
project,
user,
link_group_access: ProjectGroupLink::DEVELOPER
).execute(group)
end
set(:group) { create(:group) }
set(:user) { create(:user) }
set(:shared_project) do
shared_project = create(:project, namespace: user.namespace)
share_project(shared_project)
shared_project
end
let(:json_project_ids) { json_response.map { |project_info| project_info['id'] } }
before do
sign_in(user)
end
describe 'GET #index' do
it 'returns only projects shared with the group' do
create(:project, namespace: group)
get_shared_projects
expect(json_project_ids).to contain_exactly(shared_project.id)
end
it 'allows filtering shared projects' do
project = create(:project, :archived, namespace: user.namespace, name: "Searching for")
share_project(project)
get_shared_projects(filter: 'search')
expect(json_project_ids).to contain_exactly(project.id)
end
it 'allows sorting projects' do
shared_project.update!(name: 'bbb')
second_project = create(:project, namespace: user.namespace, name: 'aaaa')
share_project(second_project)
get_shared_projects(sort: 'name_asc')
expect(json_project_ids).to eq([second_project.id, shared_project.id])
end
end
end
......@@ -592,7 +592,7 @@ describe 'Issues' do
end
it 'clears local storage after creating a new issue', :js do
2.times.each do
2.times do
visit new_project_issue_path(project)
wait_for_requests
......
require 'spec_helper'
require_relative '../../config/initializers/grape_route_helpers_fix'
describe 'route shadowing' do
include GrapeRouteHelpers::NamedRouteMatcher
it 'does not occur' do
path = api_v4_projects_merge_requests_path(id: 1)
expect(path).to eq('/api/v4/projects/1/merge_requests')
path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3)
expect(path).to eq('/api/v4/projects/1/merge_requests/3')
end
end
{
"env": {
"jasmine": true
},
"extends": "plugin:jasmine/recommended",
"globals": {
"appendLoadFixtures": false,
"appendLoadStyleFixtures": false,
"appendSetFixtures": false,
"appendSetStyleFixtures": false,
"getJSONFixture": false,
"loadFixtures": false,
"loadJSONFixtures": false,
"loadStyleFixtures": false,
"preloadFixtures": false,
"preloadStyleFixtures": false,
"readFixtures": false,
"sandbox": false,
"setFixtures": false,
"setStyleFixtures": false,
"spyOnDependency": false,
"spyOnEvent": false,
"ClassSpecHelper": false
},
"plugins": ["jasmine"],
"rules": {
"func-names": 0,
"jasmine/no-suite-dupes": [1, "branch"],
"jasmine/no-spec-dupes": [1, "branch"],
"no-console": 0,
"prefer-arrow-callback": 0
}
}
---
env:
jasmine: true
extends: plugin:jasmine/recommended
globals:
appendLoadFixtures: false
appendLoadStyleFixtures: false
appendSetFixtures: false
appendSetStyleFixtures: false
getJSONFixture: false
loadFixtures: false
loadJSONFixtures: false
loadStyleFixtures: false
preloadFixtures: false
preloadStyleFixtures: false
readFixtures: false
sandbox: false
setFixtures: false
setStyleFixtures: false
spyOnDependency: false
spyOnEvent: false
ClassSpecHelper: false
plugins:
- jasmine
rules:
func-names: off
jasmine/no-suite-dupes:
- warn
- branch
jasmine/no-spec-dupes:
- warn
- branch
no-console: off
prefer-arrow-callback: off
......@@ -84,9 +84,14 @@ describe('iPython notebook renderer', () => {
describe('error in JSON response', () => {
let mock;
beforeEach((done) => {
beforeEach(done => {
mock = new MockAdapter(axios);
mock.onGet('/test').reply(() => Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }));
mock
.onGet('/test')
.reply(() =>
// eslint-disable-next-line prefer-promise-reject-errors
Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }),
);
renderNotebook();
......
/* global BoardService */
import Vue from 'vue';
import '~/boards/stores/boards_store';
import BoardBlankState from '~/boards/components/board_blank_state.vue';
......
/* global List */
/* global ListAssignee */
/* global ListLabel */
/* global BoardService */
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
......
/* global BoardService */
/* global List */
/* global ListIssue */
import Vue from 'vue';
......
/* global BoardService */
/* global List */
import Vue from 'vue';
......
/* eslint-disable comma-dangle, one-var, no-unused-vars */
/* global BoardService */
/* global ListIssue */
import Vue from 'vue';
......
/* eslint-disable comma-dangle */
/* global BoardService */
/* global ListIssue */
import Vue from 'vue';
......
/* eslint-disable comma-dangle */
/* global BoardService */
/* global List */
/* global ListIssue */
......
......@@ -75,10 +75,7 @@ describe('Commit pipeline status component', () => {
describe('When polling data was not succesful', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet('/dummy/endpoint').reply(() => {
const res = Promise.reject([502, { }]);
return res;
});
mock.onGet('/dummy/endpoint').reply(502, {});
vm = new Component({
props: {
endpoint: '/dummy/endpoint',
......
import Vue from 'vue';
const mountComponent = (Component, props = {}, el = null) => new Component({
const mountComponent = (Component, props = {}, el = null) =>
new Component({
propsData: props,
}).$mount(el);
}).$mount(el);
export const createComponentWithStore = (Component, store, propsData = {}) => new Component({
export const createComponentWithStore = (Component, store, propsData = {}) =>
new Component({
store,
propsData,
});
export const createComponentWithMixin = (mixins = [], state = {}, props = {}, template = '<div></div>') => {
const Component = Vue.extend({
template,
mixins,
data() {
return props;
},
});
return mountComponent(Component, props);
};
export const mountComponentWithStore = (Component, { el, props, store }) =>
new Component({
store,
propsData: props || { },
propsData: props || {},
}).$mount(el);
export default mountComponent;
......@@ -8,10 +8,7 @@ describe('Confidential Issue Sidebar Block', () => {
beforeEach(() => {
const Component = Vue.extend(confidentialIssueSidebar);
const service = {
update: () => new Promise((resolve, reject) => {
resolve(true);
reject('failed!');
}),
update: () => Promise.resolve(true),
};
vm1 = new Component({
......
/* eslint-disable prefer-rest-params, wrap-iife,
no-unused-expressions, no-return-assign, no-param-reassign*/
no-unused-expressions, no-return-assign, no-param-reassign */
export default class MockU2FDevice {
constructor() {
......
......@@ -29,6 +29,18 @@ describe API::MergeRequests do
project.add_reporter(user)
end
describe 'route shadowing' do
include GrapePathHelpers::NamedRouteMatcher
it 'does not occur' do
path = api_v4_projects_merge_requests_path(id: 1)
expect(path).to eq('/api/v4/projects/1/merge_requests')
path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3)
expect(path).to eq('/api/v4/projects/1/merge_requests/3')
end
end
describe 'GET /merge_requests' do
context 'when unauthenticated' do
it 'returns an array of all merge requests' do
......
......@@ -382,6 +382,8 @@ describe ObjectStorage do
is_expected.to have_key(:RemoteObject)
expect(subject[:RemoteObject]).to have_key(:ID)
expect(subject[:RemoteObject]).to include(Timeout: a_kind_of(Integer))
expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DIRECT_UPLOAD_TIMEOUT)
expect(subject[:RemoteObject]).to have_key(:GetURL)
expect(subject[:RemoteObject]).to have_key(:DeleteURL)
expect(subject[:RemoteObject]).to have_key(:StoreURL)
......
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