diff options
69 files changed, 1377 insertions, 589 deletions
diff --git a/.scss-lint.yml b/.scss-lint.yml index bf95b42a25c..2326a8a8c65 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -178,6 +178,10 @@ linters: SpaceAfterComma: enabled: true + # Comment literals should be followed by a space. + SpaceAfterComment: + enabled: false + # Properties should be formatted with a single space separating the colon # from the property's value. SpaceAfterPropertyColon: @@ -240,7 +244,7 @@ linters: # Do not use parent selector references (&) when they would otherwise # be unnecessary. UnnecessaryParentReference: - enabled: false + enabled: true # URLs should be valid and not contain protocols or domain names. UrlFormat: diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 04a373efe6b..c5523bd09b1 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.16.0 +0.17.0 @@ -334,7 +334,7 @@ group :development, :test do gem 'rubocop', '~> 0.47.1', require: false gem 'rubocop-rspec', '~> 1.15.0', require: false - gem 'scss_lint', '~> 0.47.0', require: false + gem 'scss_lint', '~> 0.54.0', require: false gem 'haml_lint', '~> 0.21.0', require: false gem 'simplecov', '~> 0.14.0', require: false gem 'flay', '~> 2.8.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index bb5b3602add..c27c06e9c34 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -762,9 +762,9 @@ GEM sawyer (0.8.1) addressable (>= 2.3.5, < 2.6) faraday (~> 0.8, < 1.0) - scss_lint (0.47.1) - rake (>= 0.9, < 11) - sass (~> 3.4.15) + scss_lint (0.54.0) + rake (>= 0.9, < 13) + sass (~> 3.4.20) securecompare (1.0.0) seed-fu (2.3.6) activerecord (>= 3.1) @@ -1081,7 +1081,7 @@ DEPENDENCIES rugged (~> 0.25.1.1) sanitize (~> 2.0) sass-rails (~> 5.0.6) - scss_lint (~> 0.47.0) + scss_lint (~> 0.54.0) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) sentry-raven (~> 2.5.3) diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png Binary files differindex 8879d26d341..f98ca15d787 100644 --- a/app/assets/images/new_nav.png +++ b/app/assets/images/new_nav.png diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index 36fe8a7184f..27312d718b0 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -51,8 +51,9 @@ export default () => { methods: { loadFile() { this.$http.get(el.dataset.endpoint) + .then(response => response.json()) .then((res) => { - this.json = res.json(); + this.json = res; this.loading = false; }) .catch((e) => { diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index b94009ee76b..88b054b76e6 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -81,8 +81,9 @@ $(() => { mounted () { Store.disabled = this.disabled; gl.boardService.all() + .then(response => response.json()) .then((resp) => { - resp.json().forEach((board) => { + resp.forEach((board) => { const list = Store.addList(board, this.defaultAvatar); if (list.type === 'closed') { @@ -97,7 +98,8 @@ $(() => { Store.addBlankState(); this.loading = false; - }).catch(() => new Flash('An error occurred. Please try again.')); + }) + .catch(() => new Flash('An error occurred. Please try again.')); }, methods: { updateTokens() { diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js index 870e115bd1a..e7f16899362 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js +++ b/app/assets/javascripts/boards/components/board_blank_state.js @@ -64,8 +64,9 @@ export default { // Save the labels gl.boardService.generateDefaultLists() - .then((resp) => { - resp.json().forEach((listObj) => { + .then(resp => resp.json()) + .then((data) => { + data.forEach((listObj) => { const list = Store.findList('title', listObj.title); list.id = listObj.id; diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 6356c266ee2..1d36519c75c 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -88,9 +88,9 @@ gl.issueBoards.IssuesModal = Vue.extend({ return gl.boardService.getBacklog(queryData(this.filter.path, { page: this.page, per: this.perPage, - })).then((res) => { - const data = res.json(); - + })) + .then(resp => resp.json()) + .then((data) => { if (clearIssues) { this.issues = []; } diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index b4b09b3876e..08f7c5ddcd2 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -40,9 +40,8 @@ class List { save () { return gl.boardService.createList(this.label.id) - .then((resp) => { - const data = resp.json(); - + .then(resp => resp.json()) + .then((data) => { this.id = data.id; this.type = data.list_type; this.position = data.position; @@ -91,8 +90,8 @@ class List { } return gl.boardService.getIssuesForList(this.id, data) - .then((resp) => { - const data = resp.json(); + .then(resp => resp.json()) + .then((data) => { this.loading = false; this.issuesSize = data.size; @@ -109,8 +108,8 @@ class List { this.issuesSize += 1; return gl.boardService.newIssue(this.id, issue) - .then((resp) => { - const data = resp.json(); + .then(resp => resp.json()) + .then((data) => { issue.id = data.iid; if (this.issuesSize > 1) { diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index db9bced2f89..3742507b236 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -23,11 +23,6 @@ class BoardService { url: bulkUpdatePath, }, }); - - Vue.http.interceptors.push((request, next) => { - request.headers['X-CSRF-Token'] = $.rails.csrfToken(); - next(); - }); } all () { diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 3c77f14d533..6d31b78b36d 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -51,11 +51,11 @@ }, methods: { successCallback(resp) { - const response = resp.json(); - - // depending of the endpoint the response can either bring a `pipelines` key or not. - const pipelines = response.pipelines || response; - this.setCommonData(pipelines); + return resp.json().then((response) => { + // depending of the endpoint the response can either bring a `pipelines` key or not. + const pipelines = response.pipelines || response; + this.setCommonData(pipelines); + }); }, }, }; diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index 9d51fb53eb2..efb6ced9f46 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */ +/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */ /* global CommentsStore */ /* global ResolveService */ /* global Flash */ @@ -64,8 +64,6 @@ const ResolveBtn = Vue.extend({ }); }, resolve: function () { - const errorFlashMsg = 'An error occurred when trying to resolve a comment. Please try again.'; - if (!this.canResolve) return; let promise; @@ -79,24 +77,20 @@ const ResolveBtn = Vue.extend({ .resolve(this.noteId); } - promise.then((response) => { - this.loading = false; + promise + .then(resp => resp.json()) + .then((data) => { + this.loading = false; - if (response.status === 200) { - const data = response.json(); const resolved_by = data ? data.resolved_by : null; CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by); this.discussion.updateHeadline(data); gl.mrWidget.checkStatus(); - } else { - new Flash(errorFlashMsg); - } - this.updateTooltip(); - }).catch(() => { - new Flash(errorFlashMsg); - }); + this.updateTooltip(); + }) + .catch(() => new Flash('An error occurred when trying to resolve a comment. Please try again.')); } }, mounted: function () { diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js index 807ab11d292..2f063f6fe1f 100644 --- a/app/assets/javascripts/diff_notes/services/resolve.js +++ b/app/assets/javascripts/diff_notes/services/resolve.js @@ -1,4 +1,3 @@ -/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */ /* global Flash */ /* global CommentsStore */ @@ -32,27 +31,22 @@ class ResolveServiceClass { promise = this.resolveAll(mergeRequestId, discussionId); } - promise.then((response) => { - discussion.loading = false; - - if (response.status === 200) { - const data = response.json(); - const resolved_by = data ? data.resolved_by : null; + promise + .then(resp => resp.json()) + .then((data) => { + discussion.loading = false; + const resolvedBy = data ? data.resolved_by : null; if (isResolved) { discussion.unResolveAllNotes(); } else { - discussion.resolveAllNotes(resolved_by); + discussion.resolveAllNotes(resolvedBy); } gl.mrWidget.checkStatus(); discussion.updateHeadline(data); - } else { - throw new Error('An error occurred when trying to resolve discussion.'); - } - }).catch(() => { - new Flash('An error occurred when trying to resolve a discussion. Please try again.'); - }); + }) + .catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.')); } resolveAll(mergeRequestId, discussionId) { @@ -62,7 +56,7 @@ class ResolveServiceClass { return this.discussionResource.save({ mergeRequestId, - discussionId + discussionId, }, {}); } @@ -73,7 +67,7 @@ class ResolveServiceClass { return this.discussionResource.delete({ mergeRequestId, - discussionId + discussionId, }, {}); } } diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 25b24fbd6dc..8f4066e3a6e 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -1,17 +1,15 @@ export default { methods: { saveData(resp) { - const response = { - headers: resp.headers, - body: resp.json(), - }; + const headers = resp.headers; + return resp.json().then((response) => { + this.isLoading = false; - this.isLoading = false; - - this.store.storeAvailableCount(response.body.available_count); - this.store.storeStoppedCount(response.body.stopped_count); - this.store.storeEnvironments(response.body.environments); - this.store.setPagination(response.headers); + this.store.storeAvailableCount(response.available_count); + this.store.storeStoppedCount(response.stopped_count); + this.store.storeEnvironments(response.environments); + this.store.setPagination(headers); + }); }, }, }; diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index ff601db2aa6..00e1bd94c9c 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -99,8 +99,10 @@ document.addEventListener('DOMContentLoaded', () => { page: currentPath, }, document.title, currentPath); - this.updateGroups(response.json()); - this.updatePagination(response.headers); + return response.json().then((data) => { + this.updateGroups(data); + this.updatePagination(response.headers); + }); }) .catch(this.handleErrorResponse); }, @@ -114,18 +116,19 @@ document.addEventListener('DOMContentLoaded', () => { }, leaveGroup(group, collection) { this.service.leaveGroup(group.leavePath) + .then(resp => resp.json()) .then((response) => { $.scrollTo(0); this.store.removeGroup(group, collection); // eslint-disable-next-line no-new - new Flash(response.json().notice, 'notice'); + new Flash(response.notice, 'notice'); }) - .catch((response) => { + .catch((error) => { let message = 'An error occurred. Please try again.'; - if (response.status === 403) { + if (error.status === 403) { message = 'Failed to leave the group. Please make sure you are not the only owner'; } diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index 3d5fb7f441c..efae112923d 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -202,10 +202,7 @@ export default { this.poll = new Poll({ resource: this.service, method: 'getData', - successCallback: (res) => { - const data = res.json(); - this.store.updateState(data); - }, + successCallback: res => res.json().then(data => this.store.updateState(data)), errorCallback(err) { throw new Error(err); }, diff --git a/app/assets/javascripts/jobs/job_details_mediator.js b/app/assets/javascripts/jobs/job_details_mediator.js index 063c52fac74..cc014b815c4 100644 --- a/app/assets/javascripts/jobs/job_details_mediator.js +++ b/app/assets/javascripts/jobs/job_details_mediator.js @@ -54,9 +54,8 @@ export default class JobMediator { } successCallback(response) { - const data = response.json(); this.state.isLoading = false; - this.store.storeJob(data); + return response.json().then(data => this.store.storeJob(data)); } errorCallback() { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 1a68c5bca00..b2c503d1656 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1270,7 +1270,7 @@ export default class Notes { <div class="timeline-entry-inner"> <div class="timeline-icon"> <a href="/${currentUsername}"> - <img class="avatar s40" src="${currentUserAvatar}"> + <img class="avatar s40" src="${currentUserAvatar}" /> </a> </div> <div class="timeline-content ${discussionClass}"> diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 01ae07aad65..5df317a76bf 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -129,14 +129,11 @@ }, successCallback(resp) { - const response = { - headers: resp.headers, - body: resp.json(), - }; - - this.store.storeCount(response.body.count); - this.store.storePagination(response.headers); - this.setCommonData(response.body.pipelines); + return resp.json().then((response) => { + this.store.storeCount(response.count); + this.store.storePagination(resp.headers); + this.setCommonData(response.pipelines); + }); }, }, }; diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 87b2725a045..a4a27247406 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -73,8 +73,9 @@ export default { fetchJobs() { this.$http.get(this.stage.dropdown_path) - .then((response) => { - this.dropdownContent = response.json().html; + .then(response => response.json()) + .then((data) => { + this.dropdownContent = data.html; this.isLoading = false; }) .catch(() => { diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js index 82537ea06f5..385e7430a7d 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js @@ -40,10 +40,10 @@ export default class pipelinesMediator { } successCallback(response) { - const data = response.json(); - - this.state.isLoading = false; - this.store.storePipeline(data); + return response.json().then((data) => { + this.state.isLoading = false; + this.store.storePipeline(data); + }); } errorCallback() { diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index 5ccfb4ee9c1..721e92221cf 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -28,8 +28,8 @@ export default class SidebarMediator { fetch() { this.service.get() - .then((response) => { - const data = response.json(); + .then(response => response.json()) + .then((data) => { this.store.setAssigneeData(data); this.store.setTimeTrackingData(data); }) diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 8303c556f64..4e10bbc7408 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -44,9 +44,8 @@ text: this.$slots.textarea[0].elm.value, }, ) - .then((res) => { - const data = res.json(); - + .then(resp => resp.json()) + .then((data) => { this.markdownPreviewLoading = false; this.markdownPreview = data.body; diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js index 740930dce5b..7f8e514fda1 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js @@ -14,11 +14,22 @@ Vue.http.interceptors.push((request, next) => { }); }); -// Inject CSRF token so we don't break any tests. +// Inject CSRF token and parse headers. +// New Vue Resource version uses Headers, we are expecting a plain object to render pagination +// and polling. Vue.http.interceptors.push((request, next) => { if ($.rails) { - // eslint-disable-next-line no-param-reassign - request.headers['X-CSRF-Token'] = $.rails.csrfToken(); + request.headers.set('X-CSRF-Token', $.rails.csrfToken()); } - next(); + + next((response) => { + // Headers object has a `forEach` property that iterates through all values. + const headers = {}; + + response.headers.forEach((value, key) => { + headers[key] = value; + }); + // eslint-disable-next-line no-param-reassign + response.headers = headers; + }); }); diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index f05348ee4e3..7e4e5fd7f1c 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -368,7 +368,7 @@ margin-right: 0.3em; } - & > .value { + > .value { font-weight: 600; } } @@ -467,7 +467,7 @@ -webkit-flex-direction: column; flex-direction: column; - &> span { + > span { white-space: normal; word-break: break-all; } diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss index 6d27d7568cf..71d5949b023 100644 --- a/app/assets/stylesheets/framework/highlight.scss +++ b/app/assets/stylesheets/framework/highlight.scss @@ -61,7 +61,7 @@ &:focus { outline: none; - & i { + i { visibility: visible; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index d48dec8fe7a..2db967547dd 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -96,7 +96,7 @@ overflow: visible; } - & > span { + > span { padding-right: 4px; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 303425041df..64a48e226bc 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -250,7 +250,7 @@ ul.notes { } .note-text { - & p:first-child { + p:first-child { display: none; } diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss index dc719a6ba94..284b38ad370 100644 --- a/app/assets/stylesheets/pages/pipeline_schedules.scss +++ b/app/assets/stylesheets/pages/pipeline_schedules.scss @@ -96,12 +96,12 @@ } &:last-child { - & .pipeline-variable-row-remove-button { + .pipeline-variable-row-remove-button { display: none; } @media (max-width: $screen-sm-max) { - & .pipeline-variable-value-input { + .pipeline-variable-value-input { margin-right: $pipeline-variable-remove-button-width; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 7d7c34115f9..46434eab8f3 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -26,7 +26,7 @@ margin-bottom: 5px; } - & > .form-group { + > .form-group { padding-left: 0; } @@ -83,7 +83,7 @@ border: 1px solid $border-color; } - & + .select2 a { + + .select2 a { border-top-left-radius: 0; border-bottom-left-radius: 0; } diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index de652a79369..d7a9dda3770 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -81,7 +81,7 @@ .todo-title { display: flex; - & > .title-item { + > .title-item { -webkit-flex: 0 0 auto; flex: 0 0 auto; margin: 0 2px; diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index 94d0a39f397..45c21c5d274 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -147,13 +147,13 @@ } ul.wiki-pages-list.content-list { - & ul { + ul { list-style: none; margin-left: 0; padding-left: 15px; } - & ul li { + ul li { padding: 5px 0; } } diff --git a/app/models/user.rb b/app/models/user.rb index 4b01c2f19f0..2d39b1c1c34 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -699,7 +699,7 @@ class User < ActiveRecord::Base end def sanitize_attrs - %w[name username skype linkedin twitter].each do |attr| + %w[username skype linkedin twitter].each do |attr| value = public_send(attr) public_send("#{attr}=", Sanitize.clean(value)) if value.present? end diff --git a/changelogs/unreleased/10085-stop-encoding-user-name.yml b/changelogs/unreleased/10085-stop-encoding-user-name.yml new file mode 100644 index 00000000000..8fab474e047 --- /dev/null +++ b/changelogs/unreleased/10085-stop-encoding-user-name.yml @@ -0,0 +1,4 @@ +--- +title: "Insert user name directly without encoding" +merge_request: 10085 +author: Nathan Neulinger <nneul@neulinger.org> diff --git a/changelogs/unreleased/34325-reinstate-is_admin-for-user-api.yml b/changelogs/unreleased/34325-reinstate-is_admin-for-user-api.yml new file mode 100644 index 00000000000..3bed1fbe16e --- /dev/null +++ b/changelogs/unreleased/34325-reinstate-is_admin-for-user-api.yml @@ -0,0 +1,4 @@ +--- +title: Return `is_admin` attribute in the GET /user endpoint for admins +merge_request: 12811 +author: diff --git a/changelogs/unreleased/34534-update-vue-resource.yml b/changelogs/unreleased/34534-update-vue-resource.yml new file mode 100644 index 00000000000..2d0af0c9bfe --- /dev/null +++ b/changelogs/unreleased/34534-update-vue-resource.yml @@ -0,0 +1,4 @@ +--- +title: Updates vue resource and code according to breaking changes +merge_request: +author: diff --git a/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml b/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml new file mode 100644 index 00000000000..e6cd834aed2 --- /dev/null +++ b/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml @@ -0,0 +1,4 @@ +--- +title: Bump scss-lint to 0.54.0 +merge_request: 12733 +author: Takuya Noguchi diff --git a/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml b/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml new file mode 100644 index 00000000000..25cc8b5e45f --- /dev/null +++ b/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml @@ -0,0 +1,4 @@ +--- +title: Remove public/ci/favicon.ico +merge_request: 12803 +author: Takuya Noguchi diff --git a/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml b/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml new file mode 100644 index 00000000000..59d5df56525 --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml @@ -0,0 +1,4 @@ +--- +title: Enable UnnecessaryParentReference in scss-lint +merge_request: 12738 +author: Takuya Noguchi diff --git a/changelogs/unreleased/replace_spinach_spec_browse_files.yml b/changelogs/unreleased/replace_spinach_spec_browse_files.yml new file mode 100644 index 00000000000..7380d39fa9f --- /dev/null +++ b/changelogs/unreleased/replace_spinach_spec_browse_files.yml @@ -0,0 +1,4 @@ +--- +title: Replace 'browse_files.feature' spinach test with an rspec analog +merge_request: 12251 +author: @blackst0ne diff --git a/doc/api/users.md b/doc/api/users.md index 91170e79645..6e5ec3231c5 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -364,7 +364,7 @@ GET /user Parameters: -- `sudo` (required) - the ID of a user +- `sudo` (optional) - the ID of a user to make the call in their place ``` GET /user diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index a984bb6c94c..0742b202807 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -112,7 +112,50 @@ Vue Resource should only be imported in the service file. Vue.use(VueResource); ``` -### CSRF token +#### 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` @@ -126,13 +169,13 @@ The following example shows an application: // store.js export default class Store { - /** + /** * This is where we will iniatialize the state of our data. * Usually in a small SPA you don't need any options when starting the store. In the case you do * need guarantee it's an Object and it's documented. - * - * @param {Object} options - */ + * + * @param {Object} options + */ constructor(options) { this.options = options; @@ -205,14 +248,14 @@ import Store from 'store'; import Service from 'service'; import TodoComponent from 'todoComponent'; export default { - /** + /** * Although most data belongs in the store, each component it's own state. * We want to show a loading spinner while we are fetching the todos, this state belong * in the component. * * We need to access the store methods through all methods of our component. * We need to access the state of our store. - */ + */ data() { const store = new Store(); @@ -396,42 +439,46 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e [Vue Resource Interceptors][vue-resource-interceptor] allow us to add a interceptor with the response we need: -```javascript - // Mock the service to return data - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify([{ - title: 'This is a todo', - body: 'This is the text' - }]), { - status: 200, - })); - }; + ```javascript + // Mock the service to return data + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify([{ + title: 'This is a todo', + body: 'This is the text' + }]), { + status: 200, + })); + }; - beforeEach(() => { - Vue.http.interceptors.push(interceptor); - }); + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + }); - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); - }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + }); - it('should do something', (done) => { - setTimeout(() => { - // Test received data - done(); - }, 0); - }); -``` + it('should do something', (done) => { + setTimeout(() => { + // Test received data + done(); + }, 0); + }); + ``` + +1. Headers interceptor +Refer to [this section](vue.md#headers) 1. Use `$.mount()` to mount the component + ```javascript - // bad - new Component({ - el: document.createElement('div') - }); +// bad +new Component({ + el: document.createElement('div') +}); - // good - new Component().$mount(); +// good +new Component().$mount(); ``` [vue-docs]: http://vuejs.org/guide/index.html diff --git a/doc/install/requirements.md b/doc/install/requirements.md index a3d676433e6..141df55f6bc 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -69,7 +69,7 @@ so keep in mind that you need at least 4GB available before running GitLab. With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage. -- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice. +- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the [unicorn worker section below](#unicorn-workers) for more advice. - 2GB RAM + 2GB swap supports up to 100 users but it will be very slow - **4GB RAM** is the **recommended** memory size for all installations and supports up to 100 users - 8GB RAM supports up to 1,000 users diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature deleted file mode 100644 index 59a625056d6..00000000000 --- a/features/project/source/browse_files.feature +++ /dev/null @@ -1,323 +0,0 @@ -Feature: Project Source Browse Files - Background: - Given I sign in as a user - And I own project "Shop" - Given I visit project source page - - Scenario: I browse files from master branch - Then I should see files from repository - - Scenario: I browse files for specific ref - Given I visit project source page for "6d39438" - Then I should see files from repository for "6d39438" - - @javascript - Scenario: I browse file content - Given I click on ".gitignore" file in repo - Then I should see its content - - Scenario: I browse raw file - Given I visit blob file from repo - And I click link "Raw" - Then I should see raw file content - - Scenario: I can create file - Given I click on "New file" link in repo - Then I can see new file page - - Scenario: I can create file when I don't have write access - Given I don't have write access - And I click on "New file" link in repo - Then I should see a notice about a new fork having been created - Then I can see new file page - - @javascript - Scenario: I can create and commit file - Given I click on "New file" link in repo - And I edit code - And I fill the new file name - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the new file - And I should see its new content - - @javascript - Scenario: I can create and commit file when I don't have write access - Given I don't have write access - And I click on "New file" link in repo - And I edit code - And I fill the new file name - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the fork's new merge request page - And I can see the new commit message - - @javascript - Scenario: I can create and commit file with new lines at the end of file - Given I click on "New file" link in repo - And I edit code with new lines at end of file - And I fill the new file name - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the new file - And I click button "Edit" - And I should see its content with new lines preserved at end of file - - @javascript - Scenario: I can create and commit file and specify new branch - Given I click on "New file" link in repo - And I edit code - And I fill the new file name - And I fill the commit message - And I fill the new branch name - And I click on "Commit changes" - Then I am redirected to the new merge request page - When I click on "Changes" tab - And I should see its new content - - @javascript - Scenario: I can upload file and commit - Given I click on "Upload file" link in repo - And I upload a new text file - And I fill the upload file commit message - And I fill the new branch name - And I click on "Upload file" - Then I can see the new commit message - And I am redirected to the new merge request page - When I click on "Changes" tab - Then I can see the new text file - - @javascript - Scenario: I can upload file and commit when I don't have write access - Given I don't have write access - And I click on "Upload file" link in repo - Then I should see a notice about a new fork having been created - When I click on "Upload file" link in repo - And I upload a new text file - And I fill the upload file commit message - And I click on "Upload file" - Then I can see the new commit message - And I am redirected to the fork's new merge request page - When I click on "Changes" tab - Then I can see the new text file - - @javascript - Scenario: I can replace file and commit - Given I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Replace" - And I replace it with a text file - And I fill the replace file commit message - And I click on "Replace file" - Then I can see the new text file - And I can see the replacement commit message - - @javascript - Scenario: I can replace file and commit when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Replace" - Then I should see a Fork/Cancel combo - And I click button "Fork" - Then I should see a notice about a new fork having been created - When I click on "Replace" - And I replace it with a text file - And I fill the replace file commit message - And I click on "Replace file" - And I can see the replacement commit message - And I am redirected to the fork's new merge request page - When I click on "Changes" tab - Then I can see the new text file - - @javascript - Scenario: I can create file with a directory name - Given I click on "New file" link in repo - And I fill the new file name with a new directory - And I edit code - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the new file with directory - And I should see its new content - - @javascript - Scenario: I can edit file - Given I click on ".gitignore" file in repo - And I click button "Edit" - Then I can edit code - - @javascript - Scenario: I can edit file when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I click button "Edit" - Then I should see a Fork/Cancel combo - And I click button "Fork" - Then I should see a notice about a new fork having been created - And I can edit code - - Scenario: If the file is binary the edit link is hidden - Given I visit a binary file in the repo - Then I cannot see the edit button - - @javascript - Scenario: I can edit and commit file - Given I click on ".gitignore" file in repo - And I click button "Edit" - And I edit code - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the ".gitignore" - And I should see its new content - - @javascript - Scenario: I can edit and commit file when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I click button "Edit" - Then I should see a Fork/Cancel combo - And I click button "Fork" - And I edit code - And I fill the commit message - And I click on "Commit changes" - Then I am redirected to the fork's new merge request page - And I can see the new commit message - - @javascript - Scenario: I can edit and commit file to new branch - Given I click on ".gitignore" file in repo - And I click button "Edit" - And I edit code - And I fill the commit message - And I fill the new branch name - And I click on "Commit changes" - Then I am redirected to the new merge request page - Then I click on "Changes" tab - And I should see its new content - - @javascript @wip - Scenario: If I don't change the content of the file I see an error message - Given I click on ".gitignore" file in repo - And I click button "edit" - And I fill the commit message - And I click on "Commit changes" - # Test fails because carriage returns are added to the file. - Then I am on the ".gitignore" edit file page - And I see a commit error message - - @javascript - Scenario: I can create directory in repo - When I click on "New directory" link in repo - And I fill the new directory name - And I fill the commit message - And I fill the new branch name - And I click on "Create directory" - Then I am redirected to the new merge request page - - @javascript - Scenario: I can create directory in repo when I don't have write access - Given I don't have write access - When I click on "New directory" link in repo - Then I should see a notice about a new fork having been created - When I click on "New directory" link in repo - And I fill the new directory name - And I fill the commit message - And I click on "Create directory" - Then I am redirected to the fork's new merge request page - - @javascript - Scenario: I attempt to create an existing directory - When I click on "New directory" link in repo - And I fill an existing directory name - And I fill the commit message - And I click on "Create directory" - Then I see "Unable to create directory" - And I am redirected to the root directory - - @javascript - Scenario: I can see editing preview - Given I click on ".gitignore" file in repo - And I click button "Edit" - And I edit code - And I click link "Diff" - Then I see diff - - @javascript - Scenario: I can delete file and commit - Given I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Delete" - And I fill the commit message - And I click on "Delete file" - Then I am redirected to the files URL - And I don't see the ".gitignore" - - @javascript - Scenario: I can delete file and commit when I don't have write access - Given I don't have write access - And I click on ".gitignore" file in repo - And I see the ".gitignore" - And I click on "Delete" - Then I should see a Fork/Cancel combo - And I click button "Fork" - Then I should see a notice about a new fork having been created - When I click on "Delete" - And I fill the commit message - And I click on "Delete file" - Then I am redirected to the fork's new merge request page - And I can see the new commit message - - Scenario: I can browse directory with Browse Dir - Given I click on files directory - And I click on History link - Then I see Browse dir link - - Scenario: I can browse file with Browse File - Given I click on readme file - And I click on History link - Then I see Browse file link - - Scenario: I can browse code with Browse Code - Given I click on History link - Then I see Browse code link - - # Permalink - - Scenario: I click on the permalink link from a branch ref - Given I click on ".gitignore" file in repo - And I click on Permalink - Then I am redirected to the permalink URL - - Scenario: I don't see the permalink link from a SHA ref - Given I visit project source page for "6d394385cf567f80a8fd85055db1ab4c5295806f" - And I click on ".gitignore" file in repo - Then I don't see the permalink link - - @javascript - Scenario: I browse code with single quotes in the ref - Given I switch ref to 'test' - And I see the ref 'test' has been selected - And I visit the 'test' tree - Then I see the commit data - - @javascript - Scenario: I browse code with a leading dot in the directory - Given I switch ref to fix - And I visit the fix tree - Then I see the commit data for a directory with a leading dot - - Scenario: I browse LFS object - Given I click on "files/lfs/lfs_object.iso" file in repo - Then I should see download link and object size - And I should not see lfs pointer details - And I should see buttons for allowed commands - - @javascript - Scenario: I preview an SVG file - Given I click on "Upload file" link in repo - And I upload a new SVG file - And I fill the upload file commit message - And I fill the new branch name - And I click on "Upload file" - Given I visit the SVG file - Then I can see the new rendered SVG image diff --git a/lib/api/users.rb b/lib/api/users.rb index c469751c31c..81c68ea2658 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -421,7 +421,16 @@ module API success Entities::UserPublic end get do - present current_user, with: sudo? ? Entities::UserWithPrivateDetails : Entities::UserPublic + entity = + if sudo? + Entities::UserWithPrivateDetails + elsif current_user.admin? + Entities::UserWithAdmin + else + Entities::UserPublic + end + + present current_user, with: entity end desc "Get the currently authenticated user's SSH keys" do diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index db992021403..c5600721d49 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -4,13 +4,13 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-07-02 10:32-0400\n" +"PO-Revision-Date: 2017-07-12 05:45-0400\n" "Last-Translator: Paolo Falomo <info@paolofalomo.it>\n" -"Language-Team: Italian\n" +"Language-Team: Italian (https://translate.zanata.org/project/view/GitLab)\n" "Language: it\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -33,6 +33,14 @@ msgstr[1] "%d commit" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} ha committato %{commit_timeago}" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "1 pipeline" +msgstr[1] "%d pipeline" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "Un insieme di grafici riguardo la Continuous Integration" + msgid "About auto deploy" msgstr "Riguardo il rilascio automatico" @@ -196,6 +204,9 @@ msgid_plural "Commits" msgstr[0] "Commit" msgstr[1] "Commits" +msgid "Commit duration in minutes for last 30 commits" +msgstr "Durata del commit (in minuti) per gli ultimi 30 commit" + msgid "Commit message" msgstr "Messaggio del commit" @@ -235,6 +246,13 @@ msgstr "Copia l'SHA del commit negli appunti" msgid "Create New Directory" msgstr "Crea una nuova cartella" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "" +"Creare un token di accesso sul tuo account per eseguire pull o push tramite " +"%{protocol}" + msgid "Create directory" msgstr "Crea cartella" @@ -253,6 +271,9 @@ msgstr "Fork" msgid "CreateTag|Tag" msgstr "Tag" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "Crea token d'accesso personale" + msgid "Cron Timezone" msgstr "Cron Timezone" @@ -425,6 +446,15 @@ msgstr "Intervallo di Pattern" msgid "Introducing Cycle Analytics" msgstr "Introduzione delle Analisi Cicliche" +msgid "Jobs for last month" +msgstr "Jobs dell'ultimo mese" + +msgid "Jobs for last week" +msgstr "Jobs dell'ultima settimana" + +msgid "Jobs for last year" +msgstr "Jobs dell'ultimo anno" + msgid "LFSStatus|Disabled" msgstr "Disabilitato" @@ -590,6 +620,21 @@ msgstr "Pianificazione Pipeline" msgid "Pipeline Schedules" msgstr "Pianificazione multipla Pipeline" +msgid "PipelineCharts|Failed:" +msgstr "Fallita:" + +msgid "PipelineCharts|Overall statistics" +msgstr "Statistiche riassuntive" + +msgid "PipelineCharts|Success ratio:" +msgstr "Percentuale di successo" + +msgid "PipelineCharts|Successful:" +msgstr "Completata:" + +msgid "PipelineCharts|Total:" +msgstr "Totale:" + msgid "PipelineSchedules|Activated" msgstr "Attivata" @@ -620,6 +665,18 @@ msgstr "Target" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Personalizzato" +msgid "Pipelines" +msgstr "Pipeline" + +msgid "Pipelines charts" +msgstr "Grafici pipeline" + +msgid "Pipeline|all" +msgstr "tutto" + +msgid "Pipeline|success" +msgstr "successo" + msgid "Pipeline|with stage" msgstr "con stadio" @@ -684,7 +741,7 @@ msgid "ProjectNetworkGraph|Graph" msgstr "Grafico" msgid "Read more" -msgstr "Continua..." +msgstr "Vedi altro" msgid "Readme" msgstr "Leggimi" @@ -751,7 +808,7 @@ msgstr "Seleziona una branch di destinazione" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" -"Imposta una password sul tuo account per eseguire pull o push tramite " +"Establezca una contraseña en su cuenta para actualizar o enviar a través de " "%{protocol}." msgid "Set up CI" @@ -814,9 +871,9 @@ msgid "" "the issue to a milestone, or add the issue to a list on your Issue Board. " "Begin creating issues to see data for this stage." msgstr "" -"Questo stadio di issue mostra il tempo che ci vuole dal creare un issue " -"all'assegnarli una milestone, o ad aggiungere un issue alla tua board. Crea " -"un issue per vedere questo stadio." +"Lo stadio di Issue mostra il tempo che impiega un issue ad esser correlato " +"ad una Milestone, o ad esser aggiunto ad una tua Lavagna. Inizia la " +"creazione di problemi per visualizzare i dati in questo stadio." msgid "The phase of the development lifecycle." msgstr "Il ciclo vitale della fase di sviluppo." diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index fa0b3b339fa..d5a3c5ea0b9 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -1,18 +1,18 @@ # Huang Tao <htve@outlook.com>, 2017. #zanata -# Lin Jen-Shin <anonymous@domain.com>, 2017. # Hazel Yang <anonymous@domain.com>, 2017. # TzeKei Lee <anonymous@domain.com>, 2017. # Jerry Ho <a29988122@gmail.com>, 2017. +# Lin Jen-Shin <godfat@godfat.org>, 2017. #zanata msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-06-19 15:50-0500\n" +"POT-Creation-Date: 2017-06-28 13:32+0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2017-06-28 11:13-0400\n" -"Last-Translator: Huang Tao <htve@outlook.com>\n" +"PO-Revision-Date: 2017-07-11 09:10-0400\n" +"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n" "Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n" "Language: zh-TW\n" "X-Generator: Zanata 3.9.6\n" @@ -30,6 +30,13 @@ msgstr[0] "%d 個更動 (commit)" msgid "%{commit_author_link} committed %{commit_timeago}" msgstr "%{commit_author_link} 在 %{commit_timeago} 送交" +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "%d 條流水線" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "持續整合 (CI) 相關的圖表" + msgid "About auto deploy" msgstr "關於自動部署" @@ -187,6 +194,9 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "更動記錄 (commit) " +msgid "Commit duration in minutes for last 30 commits" +msgstr "最近 30 次更動花費的時間(分鐘)" + msgid "Commit message" msgstr "更動說明 (commit) " @@ -226,6 +236,11 @@ msgstr "複製更動記錄 (commit) 的 SHA 值到剪貼簿" msgid "Create New Directory" msgstr "建立新目錄" +msgid "" +"Create a personal access token on your account to pull or push via " +"%{protocol}." +msgstr "建立個人存取憑證 (access token) 以使用 %{protocol} 來上傳 (push) 或下載 (pull) 。" + msgid "Create directory" msgstr "建立目錄" @@ -244,6 +259,9 @@ msgstr "分支 (fork) " msgid "CreateTag|Tag" msgstr "建立標籤" +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "建立個人存取憑證 (access token)" + msgid "Cron Timezone" msgstr "Cron 時區" @@ -408,6 +426,15 @@ msgstr "循環週期" msgid "Introducing Cycle Analytics" msgstr "週期分析簡介" +msgid "Jobs for last month" +msgstr "上個月的任務 (job) " + +msgid "Jobs for last week" +msgstr "上個星期的任務 (job) " + +msgid "Jobs for last year" +msgstr "去年的任務 (job) " + msgid "LFSStatus|Disabled" msgstr "停用" @@ -570,6 +597,21 @@ msgstr "流水線 (pipeline) 排程" msgid "Pipeline Schedules" msgstr "流水線 (pipeline) 排程" +msgid "PipelineCharts|Failed:" +msgstr "失敗:" + +msgid "PipelineCharts|Overall statistics" +msgstr "總體統計" + +msgid "PipelineCharts|Success ratio:" +msgstr "成功比率:" + +msgid "PipelineCharts|Successful:" +msgstr "成功:" + +msgid "PipelineCharts|Total:" +msgstr "總計:" + msgid "PipelineSchedules|Activated" msgstr "是否啟用" @@ -600,6 +642,18 @@ msgstr "目標" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "自訂" +msgid "Pipelines" +msgstr "流水線 (pipeline) " + +msgid "Pipelines charts" +msgstr "流水線 (pipeline) 圖表" + +msgid "Pipeline|all" +msgstr "所有" + +msgid "Pipeline|success" +msgstr "成功" + msgid "Pipeline|with stage" msgstr "於階段" diff --git a/package.json b/package.json index 5a997e813f8..fd944531a6a 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "visibilityjs": "^1.2.4", "vue": "^2.2.6", "vue-loader": "^11.3.4", - "vue-resource": "^0.9.3", + "vue-resource": "^1.3.4", "vue-template-compiler": "^2.2.6", "webpack": "^2.6.1", "webpack-bundle-analyzer": "^2.8.2" diff --git a/public/ci/favicon.ico b/public/ci/favicon.ico Binary files differdeleted file mode 100644 index 9663d4d00b9..00000000000 --- a/public/ci/favicon.ico +++ /dev/null diff --git a/spec/features/projects/user_browses_files_spec.rb b/spec/features/projects/user_browses_files_spec.rb new file mode 100644 index 00000000000..263a3a29a66 --- /dev/null +++ b/spec/features/projects/user_browses_files_spec.rb @@ -0,0 +1,188 @@ +require 'spec_helper' + +describe 'User browses files' do + include DropzoneHelper + + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:tree_path_ref_6d39438) { project_tree_path(project, '6d39438') } + let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + context 'when browsing the master branch' do + before do + visit(tree_path_root_ref) + end + + it 'shows files from a repository' do + expect(page).to have_content('VERSION') + expect(page).to have_content('.gitignore') + expect(page).to have_content('LICENSE') + end + + it 'shows the "Browse Directory" link' do + click_link('files') + click_link('History') + + expect(page).to have_link('Browse Directory') + expect(page).not_to have_link('Browse Code') + end + + it 'shows the "Browse File" link' do + page.within('.tree-table') do + click_link('README.md') + end + click_link('History') + + expect(page).to have_link('Browse File') + expect(page).not_to have_link('Browse Files') + end + + it 'shows the "Browse Code" link' do + click_link('History') + + expect(page).to have_link('Browse Files') + expect(page).not_to have_link('Browse Directory') + end + + it 'redirects to the permalink URL' do + click_link('.gitignore') + click_link('Permalink') + + permalink_path = project_blob_path(project, "#{project.repository.commit.sha}/.gitignore") + + expect(current_path).to eq(permalink_path) + end + end + + context 'when browsing a specific ref' do + before do + visit(tree_path_ref_6d39438) + end + + it 'shows files from a repository for "6d39438"' do + expect(current_path).to eq(tree_path_ref_6d39438) + expect(page).to have_content('.gitignore') + expect(page).to have_content('LICENSE') + end + + it 'shows files from a repository with apostroph in its name', js: true do + first('.js-project-refs-dropdown').click + + page.within('.project-refs-form') do + click_link("'test'") + end + + expect(page).to have_selector('.dropdown-toggle-text', text: "'test'") + + visit(project_tree_path(project, "'test'")) + + expect(page).to have_css('.tree-commit-link', visible: true) + expect(page).not_to have_content('Loading commit data...') + end + + it 'shows the code with a leading dot in the directory', js: true do + first('.js-project-refs-dropdown').click + + page.within('.project-refs-form') do + click_link('fix') + end + + visit(project_tree_path(project, 'fix/.testdir')) + + expect(page).to have_css('.tree-commit-link', visible: true) + expect(page).not_to have_content('Loading commit data...') + end + + it 'does not show the permalink link' do + click_link('.gitignore') + + expect(page).not_to have_link('permalink') + end + end + + context 'when browsing a file content' do + before do + visit(tree_path_root_ref) + click_link('.gitignore') + end + + it 'shows a file content', js: true do + wait_for_requests + expect(page).to have_content('*.rbc') + end + end + + context 'when browsing a raw file' do + before do + visit(project_blob_path(project, File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path))) + end + + it 'shows a raw file content' do + click_link('Open raw') + expect(source).to eq('') # Body is filled in by gitlab-workhorse + end + end + + context 'when browsing an LFS object' do + before do + allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true) + visit(project_tree_path(project, 'lfs')) + end + + it 'shows an LFS object' do + click_link('files') + click_link('lfs') + click_link('lfs_object.iso') + + expect(page).to have_content('Download (1.5 MB)') + expect(page).not_to have_content('version https://git-lfs.github.com/spec/v1') + expect(page).not_to have_content('oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897') + expect(page).not_to have_content('size 1575078') + + page.within('.content') do + expect(page).to have_content('Delete') + expect(page).to have_content('History') + expect(page).to have_content('Permalink') + expect(page).to have_content('Replace') + expect(page).not_to have_content('Annotate') + expect(page).not_to have_content('Blame') + expect(page).not_to have_content('Edit') + expect(page).to have_link('Download') + end + end + end + + context 'when previewing a file content' do + before do + visit(tree_path_root_ref) + end + + it 'shows a preview of a file content', js: true do + find('.add-to-tree').click + click_link('Upload file') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'New commit message') + end + + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Upload file') + + visit(project_blob_path(project, 'new_branch_name/logo_sample.svg')) + + expect(page).to have_css('.file-content img') + end + end +end diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb deleted file mode 100644 index 5e302da8a63..00000000000 --- a/spec/features/projects/user_create_dir_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -feature 'New directory creation', feature: true, js: true do - given(:user) { create(:user) } - given(:role) { :developer } - given(:project) { create(:project) } - - background do - sign_in(user) - project.team << [user, role] - visit project_tree_path(project, 'master') - open_new_directory_modal - fill_in 'dir_name', with: 'new_directory' - end - - def open_new_directory_modal - first('.add-to-tree').click - click_link 'New directory' - end - - def create_directory - click_button 'Create directory' - end - - context 'with default target branch' do - background do - create_directory - end - - scenario 'creates the directory in the default branch' do - expect(page).to have_content 'master' - expect(page).to have_content 'The directory has been successfully created' - expect(page).to have_content 'new_directory' - end - end - - context 'with a new target branch' do - given(:new_branch_name) { 'new-feature' } - - background do - fill_in :branch_name, with: new_branch_name - create_directory - end - - scenario 'creates the directory in the new branch' do - expect(page).to have_content new_branch_name - expect(page).to have_content 'The directory has been successfully created' - end - - scenario 'redirects to the merge request' do - expect(page).to have_content 'New Merge Request' - expect(page).to have_content "From #{new_branch_name} into master" - expect(page).to have_content 'Add new directory' - expect(current_path).to eq(project_new_merge_request_path(project)) - end - end -end diff --git a/spec/features/projects/user_creates_directory_spec.rb b/spec/features/projects/user_creates_directory_spec.rb new file mode 100644 index 00000000000..635bd4493dd --- /dev/null +++ b/spec/features/projects/user_creates_directory_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +feature 'User creates a directory', js: true do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project) } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :developer] + sign_in(user) + visit project_tree_path(project, 'master') + end + + context 'with default target branch' do + before do + first('.add-to-tree').click + click_link('New directory') + end + + it 'creates the directory in the default branch' do + fill_in(:dir_name, with: 'new_directory') + click_button('Create directory') + + expect(page).to have_content('master') + expect(page).to have_content('The directory has been successfully created') + expect(page).to have_content('new_directory') + end + + it 'does not create a directory with a name of already existed directory' do + fill_in(:dir_name, with: 'files') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Create directory') + + expect(page).to have_content('A directory with this name already exists') + expect(current_path).to eq(project_tree_path(project, 'master')) + end + end + + context 'with a new target branch' do + before do + first('.add-to-tree').click + click_link('New directory') + fill_in(:dir_name, with: 'new_directory') + fill_in(:branch_name, with: 'new-feature') + click_button('Create directory') + end + + it 'creates the directory in the new branch and redirect to the merge request' do + expect(page).to have_content('new-feature') + expect(page).to have_content('The directory has been successfully created') + expect(page).to have_content('New Merge Request') + expect(page).to have_content('From new-feature into master') + expect(page).to have_content('Add new directory') + + expect(current_path).to eq(project_new_merge_request_path(project)) + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'creates a directory in a forked project' do + find('.add-to-tree').click + click_link('New directory') + + expect(page).to have_content(fork_message) + + find('.add-to-tree').click + click_link('New directory') + fill_in(:dir_name, with: 'new_directory') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Create directory') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + end + end +end diff --git a/spec/features/projects/user_creates_files_spec.rb b/spec/features/projects/user_creates_files_spec.rb new file mode 100644 index 00000000000..0c7f1a775c1 --- /dev/null +++ b/spec/features/projects/user_creates_files_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +describe 'User creates files' do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + context 'without commiting a new file' do + context 'when an user has write access' do + before do + visit(project_tree_path_root_ref) + end + + it 'opens new file page' do + find('.add-to-tree').click + click_link('New file') + + expect(page).to have_content('New file') + expect(page).to have_content('Commit message') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'opens new file page on a forked project' do + find('.add-to-tree').click + click_link('New file') + + expect(page).to have_selector('.file-editor') + expect(page).to have_content(fork_message) + expect(page).to have_content('New file') + expect(page).to have_content('Commit message') + end + end + end + + context 'with commiting a new file' do + context 'when an user has write access' do + before do + visit(project_tree_path_root_ref) + + find('.add-to-tree').click + click_link('New file') + end + + it 'creates and commit a new file', js: true do + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + new_file_path = project_blob_path(project, 'master/not_a_file.md') + + expect(current_path).to eq(new_file_path) + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + + it 'creates and commit a new file with new lines at the end of file', js: true do + execute_script('ace.edit("editor").setValue("Sample\n\n\n")') + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + new_file_path = project_blob_path(project, 'master/not_a_file.md') + + expect(current_path).to eq(new_file_path) + + find('.js-edit-blob').click + + expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n") + end + + it 'creates and commit a new file with a directory name', js: true do + fill_in(:file_name, with: 'foo/bar/baz.txt') + + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_blob_path(project, 'master/foo/bar/baz.txt')) + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + + it 'creates and commit a new file specifying a new branch', js: true do + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_new_merge_request_path(project)) + + click_link('Changes') + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'creates and commit new file in forked project', js: true do + find('.add-to-tree').click + click_link('New file') + + expect(page).to have_selector('.file-editor') + + execute_script("ace.edit('editor').setValue('*.rbca')") + + fill_in(:file_name, with: 'not_a_file.md') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + expect(page).to have_content('New commit message') + end + end + end +end diff --git a/spec/features/projects/user_deletes_files_spec.rb b/spec/features/projects/user_deletes_files_spec.rb new file mode 100644 index 00000000000..97e60862b4f --- /dev/null +++ b/spec/features/projects/user_deletes_files_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe 'User deletes files' do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when an user has write access' do + before do + project.team << [user, :master] + visit(project_tree_path_root_ref) + end + + it 'deletes the file', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Delete') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Delete file') + + expect(current_path).to eq(project_tree_path(project, 'master')) + expect(page).not_to have_content('.gitignore') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'deletes the file in a forked project', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Delete') + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + + expect(page).to have_content(fork_message) + + click_on('Delete') + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Delete file') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + expect(page).to have_content('New commit message') + end + end +end diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb new file mode 100644 index 00000000000..eb26f1bc123 --- /dev/null +++ b/spec/features/projects/user_edits_files_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' + +describe 'User edits files' do + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when an user has write access' do + before do + project.team << [user, :master] + visit(project_tree_path_root_ref) + end + + it 'inserts a content of a file', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + + expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') + end + + it 'does not show the edit link if a file is binary' do + binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png') + visit(project_blob_path(project, binary_file)) + + expect(page).not_to have_link('edit') + end + + it 'commits an edited file', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_blob_path(project, 'master/.gitignore')) + + wait_for_requests + + expect(page).to have_content('*.rbca') + end + + it 'commits an edited file to a new branch', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Commit changes') + + expect(current_path).to eq(project_new_merge_request_path(project)) + + click_link('Changes') + + wait_for_requests + expect(page).to have_content('*.rbca') + end + + it 'shows the diff of an edited file', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + execute_script("ace.edit('editor').setValue('*.rbca')") + click_link('Preview changes') + + expect(page).to have_css('.line_holder.new') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'inserts a content of a file in a forked project', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + + expect(page).to have_content(fork_message) + + execute_script("ace.edit('editor').setValue('*.rbca')") + + expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') + end + + it 'commits an edited file in a forked project', js: true do + click_link('.gitignore') + find('.js-edit-blob').click + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + execute_script("ace.edit('editor').setValue('*.rbca')") + fill_in(:commit_message, with: 'New commit message', visible: true) + click_button('Commit changes') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + wait_for_requests + + expect(page).to have_content('New commit message') + end + end +end diff --git a/spec/features/projects/user_replaces_files_spec.rb b/spec/features/projects/user_replaces_files_spec.rb new file mode 100644 index 00000000000..50f2ffc4bbf --- /dev/null +++ b/spec/features/projects/user_replaces_files_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe 'User replaces files' do + include DropzoneHelper + + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + sign_in(user) + end + + context 'when an user has write access' do + before do + project.team << [user, :master] + visit(project_tree_path_root_ref) + end + + it 'replaces an existed file with a new one', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Replace') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'Replacement file commit message') + end + + click_button('Replace file') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + expect(page).to have_content('Replacement file commit message') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'replaces an existed file with a new one in a forked project', js: true do + click_link('.gitignore') + + expect(page).to have_content('.gitignore') + + click_on('Replace') + + expect(page).to have_link('Fork') + expect(page).to have_button('Cancel') + + click_link('Fork') + + expect(page).to have_content(fork_message) + + click_on('Replace') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'Replacement file commit message') + end + + click_button('Replace file') + + expect(page).to have_content('Replacement file commit message') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + click_link('Changes') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + end + end +end diff --git a/spec/features/projects/user_uploads_files_spec.rb b/spec/features/projects/user_uploads_files_spec.rb new file mode 100644 index 00000000000..64a1439badd --- /dev/null +++ b/spec/features/projects/user_uploads_files_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe 'User uploads files' do + include DropzoneHelper + + let(:fork_message) do + "You're not allowed to make changes to this project directly. "\ + "A fork of this project has been created that you can make changes in, so you can submit a merge request." + end + let(:project) { create(:project, name: 'Shop') } + let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } + let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) } + let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + context 'when an user has write access' do + before do + visit(project_tree_path_root_ref) + end + + it 'uploads and commit a new file', js: true do + find('.add-to-tree').click + click_link('Upload file') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'New commit message') + end + + fill_in(:branch_name, with: 'new_branch_name', visible: true) + click_button('Upload file') + + expect(page).to have_content('New commit message') + expect(current_path).to eq(project_new_merge_request_path(project)) + + click_link('Changes') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + end + end + + context 'when an user does not have write access' do + before do + project2.team << [user, :reporter] + visit(project2_tree_path_root_ref) + end + + it 'uploads and commit a new fileto a forked project', js: true do + find('.add-to-tree').click + click_link('Upload file') + + expect(page).to have_content(fork_message) + + find('.add-to-tree').click + click_link('Upload file') + drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + + page.within('#modal-upload-blob') do + fill_in(:commit_message, with: 'New commit message') + end + + click_button('Upload file') + + expect(page).to have_content('New commit message') + + fork = user.fork_of(project2) + + expect(current_path).to eq(project_new_merge_request_path(fork)) + + click_link('Changes') + + expect(page).to have_content('Lorem ipsum dolor sit amet') + expect(page).to have_content('Sed ut perspiciatis unde omnis') + end + end +end diff --git a/spec/fixtures/api/schemas/public_api/v4/user/admin.json b/spec/fixtures/api/schemas/public_api/v4/user/admin.json new file mode 100644 index 00000000000..f733914fbf8 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/user/admin.json @@ -0,0 +1,34 @@ +{ + "type": "object", + "required": [ + "id", + "username", + "email", + "name", + "state", + "avatar_url", + "web_url", + "created_at", + "is_admin", + "bio", + "location", + "skype", + "linkedin", + "twitter", + "website_url", + "organization", + "last_sign_in_at", + "confirmed_at", + "color_scheme_id", + "projects_limit", + "current_sign_in_at", + "identities", + "can_create_group", + "can_create_project", + "two_factor_enabled", + "external" + ], + "properties": { + "$ref": "full.json" + } +} diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js index 6639a6b5e7b..0c8817a8148 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environment_spec.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import '~/flash'; import environmentsComponent from '~/environments/components/environment.vue'; import { environment, folder } from './mock_data'; +import { headersInterceptor } from '../helpers/vue_resource_helper'; describe('Environment', () => { preloadFixtures('static/environments/environments.html.raw'); @@ -25,12 +26,14 @@ describe('Environment', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); }); afterEach(() => { Vue.http.interceptors = _.without( Vue.http.interceptors, environmentsEmptyResponseInterceptor, ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); }); it('should render the empty state', (done) => { @@ -54,6 +57,10 @@ describe('Environment', () => { describe('with paginated environments', () => { const environmentsResponseInterceptor = (request, next) => { + next((response) => { + response.headers.set('X-nExt-pAge', '2'); + }); + next(request.respondWith(JSON.stringify({ environments: [environment], stopped_count: 1, @@ -73,6 +80,7 @@ describe('Environment', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); component = new EnvironmentsComponent({ el: document.querySelector('#environments-list-view'), }); @@ -82,6 +90,7 @@ describe('Environment', () => { Vue.http.interceptors = _.without( Vue.http.interceptors, environmentsResponseInterceptor, ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); }); it('should render a table with environments', (done) => { diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 350078ad5f5..fdaea5c0b0c 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import '~/flash'; import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue'; import { environmentsList } from '../mock_data'; +import { headersInterceptor } from '../../helpers/vue_resource_helper'; describe('Environments Folder View', () => { preloadFixtures('static/environments/environments_folder_view.html.raw'); @@ -36,6 +37,8 @@ describe('Environments Folder View', () => { beforeEach(() => { Vue.http.interceptors.push(environmentsResponseInterceptor); + Vue.http.interceptors.push(headersInterceptor); + component = new EnvironmentsFolderViewComponent({ el: document.querySelector('#environments-folder-list-view'), }); @@ -45,6 +48,7 @@ describe('Environments Folder View', () => { Vue.http.interceptors = _.without( Vue.http.interceptors, environmentsResponseInterceptor, ); + Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); }); it('should render a table with environments', (done) => { diff --git a/spec/javascripts/helpers/vue_resource_helper.js b/spec/javascripts/helpers/vue_resource_helper.js new file mode 100644 index 00000000000..0d1bf5e2e80 --- /dev/null +++ b/spec/javascripts/helpers/vue_resource_helper.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line import/prefer-default-export +export const headersInterceptor = (request, next) => { + next((response) => { + const headers = {}; + response.headers.forEach((value, key) => { + headers[key] = value; + }); + // eslint-disable-next-line no-param-reassign + response.headers = headers; + }); +}; diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index bc13373a27e..81ce18bf2fb 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -3,7 +3,6 @@ import '~/render_math'; import '~/render_gfm'; import issuableApp from '~/issue_show/components/app.vue'; import eventHub from '~/issue_show/event_hub'; -import Poll from '~/lib/utils/poll'; import issueShowData from '../mock_data'; function formatText(text) { @@ -11,16 +10,26 @@ function formatText(text) { } describe('Issuable output', () => { + let requestData = issueShowData.initialRequest; + document.body.innerHTML = '<span id="task_status"></span>'; + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify(requestData), { + status: 200, + })); + }; + let vm; - beforeEach(() => { + beforeEach((done) => { spyOn(eventHub, '$emit'); - spyOn(Poll.prototype, 'makeRequest'); const IssuableDescriptionComponent = Vue.extend(issuableApp); + requestData = issueShowData.initialRequest; + Vue.http.interceptors.push(interceptor); + vm = new IssuableDescriptionComponent({ propsData: { canUpdate: true, @@ -40,15 +49,17 @@ describe('Issuable output', () => { projectPath: '/', }, }).$mount(); + + setTimeout(done); }); - it('should render a title/description/edited and update title/description/edited on update', (done) => { - vm.poll.options.successCallback({ - json() { - return issueShowData.initialRequest; - }, - }); + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); + vm.poll.stop(); + }); + + it('should render a title/description/edited and update title/description/edited on update', (done) => { let editedText; Vue.nextTick() .then(() => { @@ -64,13 +75,10 @@ describe('Issuable output', () => { expect(editedText.querySelector('time')).toBeTruthy(); }) .then(() => { - vm.poll.options.successCallback({ - json() { - return issueShowData.secondRequest; - }, - }); + requestData = issueShowData.secondRequest; + vm.poll.makeRequest(); }) - .then(Vue.nextTick) + .then(() => new Promise(resolve => setTimeout(resolve))) .then(() => { expect(document.querySelector('title').innerText).toContain('2 (#1)'); expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>'); @@ -304,7 +312,7 @@ describe('Issuable output', () => { it('stops polling when deleting', (done) => { spyOn(gl.utils, 'visitUrl'); - spyOn(vm.poll, 'stop'); + spyOn(vm.poll, 'stop').and.callThrough(); spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => { resolve({ json() { @@ -347,23 +355,14 @@ describe('Issuable output', () => { describe('open form', () => { it('shows locked warning if form is open & data is different', (done) => { - vm.poll.options.successCallback({ - json() { - return issueShowData.initialRequest; - }, - }); - Vue.nextTick() .then(() => { vm.openForm(); - vm.poll.options.successCallback({ - json() { - return issueShowData.secondRequest; - }, - }); + requestData = issueShowData.secondRequest; + vm.poll.makeRequest(); }) - .then(Vue.nextTick) + .then(() => new Promise(resolve => setTimeout(resolve))) .then(() => { expect( vm.formState.lockedWarningVisible, diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb index 73518656bde..19f45ea1cb2 100644 --- a/spec/lib/gitlab/git/hook_spec.rb +++ b/spec/lib/gitlab/git/hook_spec.rb @@ -2,6 +2,12 @@ require 'spec_helper' require 'fileutils' describe Gitlab::Git::Hook, lib: true do + before do + # We need this because in the spec/spec_helper.rb we define it like this: + # allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_call_original + end + describe "#trigger" do let(:project) { create(:project, :repository) } let(:repo_path) { project.repository.path } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d04162a527f..c70f916a8bd 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1159,6 +1159,18 @@ describe User, models: true do end end + describe '#sanitize_attrs' do + let(:user) { build(:user, name: 'test & user', skype: 'test&user') } + + it 'encodes HTML entities in the Skype attribute' do + expect { user.sanitize_attrs }.to change { user.skype }.to('test&user') + end + + it 'does not encode HTML entities in the name attribute' do + expect { user.sanitize_attrs }.not_to change { user.name } + end + end + describe '#starred?' do it 'determines if user starred a project' do user = create :user diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index a2368c9d996..877bde3b9a6 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -943,11 +943,11 @@ describe API::Users do expect(response).to have_http_status(403) end - it 'returns initial current user without private token when sudo not defined' do + it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin_personal_access_token}") expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/public') + expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['id']).to eq(admin.id) end end @@ -961,11 +961,11 @@ describe API::Users do expect(json_response['id']).to eq(user.id) end - it 'returns initial current user without private token when sudo not defined' do + it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin.private_token}") expect(response).to have_http_status(200) - expect(response).to match_response_schema('public_api/v4/user/public') + expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['id']).to eq(admin.id) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dc0bdd9f4c7..3c142764e28 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -76,6 +76,11 @@ RSpec.configure do |config| TestEnv.cleanup end + config.before(:example) do + # Skip pre-receive hook check so we can use the web editor and merge. + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + end + config.before(:example, :request_store) do RequestStore.begin! end diff --git a/spec/support/dropzone_helper.rb b/spec/support/dropzone_helper.rb index 02fdeb08afe..fe72d320fcf 100644 --- a/spec/support/dropzone_helper.rb +++ b/spec/support/dropzone_helper.rb @@ -54,4 +54,23 @@ module DropzoneHelper loop until page.evaluate_script('window._dropzoneComplete === true') end end + + def drop_in_dropzone(file_path) + # Generate a fake input selector + page.execute_script <<-JS + var fakeFileInput = window.$('<input/>').attr( + {id: 'fakeFileInput', type: 'file'} + ).appendTo('body'); + JS + + # Attach the file to the fake input selector with Capybara + attach_file('fakeFileInput', file_path) + + # Add the file to a fileList array and trigger the fake drop event + page.execute_script <<-JS + var fileList = [$('#fakeFileInput')[0].files[0]]; + var e = jQuery.Event('drop', { dataTransfer : { files : fileList } }); + $('.dropzone')[0].dropzone.listeners[0].events.drop(e); + JS + end end diff --git a/yarn.lock b/yarn.lock index b04eebe60af..98da6a984d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1575,6 +1575,12 @@ deckar01-task_list@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.0.0.tgz#7f7a595430d21b3036ed5dfbf97d6b65de18e2c9" +decompress-response@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + dependencies: + mimic-response "^1.0.0" + deep-extend@~0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" @@ -1712,6 +1718,10 @@ dropzone@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2445,6 +2455,10 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + getpass@^0.1.1: version "0.1.6" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" @@ -2521,6 +2535,25 @@ got@^3.2.0: read-all-stream "^3.0.0" timed-out "^2.0.0" +got@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" + dependencies: + decompress-response "^3.2.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-plain-obj "^1.1.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + p-cancelable "^0.3.0" + p-timeout "^1.1.1" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + url-parse-lax "^1.0.0" + url-to-options "^1.0.1" + graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2578,6 +2611,16 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-symbol-support-x@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8" + +has-to-string-tag-x@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.3.0.tgz#78e3d98c3c0ec9413e970eb8d766249a1e13058f" + dependencies: + has-symbol-support-x "^1.3.0" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2902,6 +2945,10 @@ is-number@^2.0.2, is-number@^2.1.0: dependencies: kind-of "^3.0.2" +is-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -2918,7 +2965,7 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" -is-plain-obj@^1.0.0: +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -2950,6 +2997,10 @@ is-resolvable@^1.0.0: dependencies: tryit "^1.0.1" +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + is-stream@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -3087,6 +3138,13 @@ istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + jasmine-core@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.3.tgz#45072950e4a42b1e322fe55c001100a465d77815" @@ -3633,6 +3691,10 @@ mime@1.3.4, mime@1.3.x, mime@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" +mimic-response@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -3981,6 +4043,14 @@ osenv@^0.1.0: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-cancelable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + p-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" @@ -3991,6 +4061,12 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-timeout@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c" + dependencies: + p-finally "^1.0.0" + package-json@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0" @@ -4419,7 +4495,7 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" -prepend-http@^1.0.0: +prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -5384,6 +5460,10 @@ timed-out@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + timers-browserify@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" @@ -5545,6 +5625,12 @@ url-loader@^0.5.8: loader-utils "^1.0.2" mime "1.3.x" +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + dependencies: + prepend-http "^1.0.1" + url-parse@1.0.x: version "1.0.5" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" @@ -5559,6 +5645,10 @@ url-parse@^1.0.1, url-parse@^1.1.1: querystringify "0.0.x" requires-port "1.0.x" +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -5657,9 +5747,11 @@ vue-loader@^11.3.4: vue-style-loader "^2.0.0" vue-template-es2015-compiler "^1.2.2" -vue-resource@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d" +vue-resource@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.3.4.tgz#9fc0bdf6a2f5cab430129fc99d347b3deae7b099" + dependencies: + got "^7.0.0" vue-style-loader@^2.0.0: version "2.0.5" |