diff options
author | Phil Hughes <me@iamphill.com> | 2017-04-27 16:56:30 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-04-27 16:56:30 +0100 |
commit | 94b93e49d2d6299dd0f1bf2d06c089f65cc7b26f (patch) | |
tree | de4f860d9d728216b353c825ea314f7328b141a9 | |
parent | 429a42b0cd2f7dd20e8e6bc924ecc27b55500410 (diff) | |
parent | 4d4aefc561bc5a9462dc68db8715b96c6c86bbca (diff) | |
download | gitlab-ce-94b93e49d2d6299dd0f1bf2d06c089f65cc7b26f.tar.gz |
Merge branch 'master' into emoji-button-titles
54 files changed, 642 insertions, 438 deletions
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index 6bb21e6a3af..241d90a204b 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -26,6 +26,7 @@ logs, and code as it's very hard to read otherwise.) #### Results of GitLab environment info <details> +<pre> (For installations with omnibus-gitlab package run and paste the output of: `sudo gitlab-rake gitlab:env:info`) @@ -33,11 +34,13 @@ logs, and code as it's very hard to read otherwise.) (For installations from source run and paste the output of: `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) +</pre> </details> #### Results of GitLab application Check <details> +<pre> (For installations with omnibus-gitlab package run and paste the output of: `sudo gitlab-rake gitlab:check SANITIZE=true`) @@ -47,6 +50,7 @@ logs, and code as it's very hard to read otherwise.) (we will only investigate if the tests are passing) +</pre> </details> ### Possible fixes diff --git a/Gemfile.lock b/Gemfile.lock index 4c3b2480b54..52707628748 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -429,7 +429,7 @@ GEM multi_json (~> 1.10) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.4) + mail (2.6.5) mime-types (>= 1.16, < 4) mail_room (0.9.1) memoist (0.15.0) diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js index 68a1c1de1df..e704be8b53e 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js @@ -106,15 +106,6 @@ export default Vue.component('pipelines-table', { eventHub.$on('refreshPipelines', this.fetchPipelines); }, - beforeUpdate() { - if (this.state.pipelines.length && - this.$children && - !this.isMakingRequest && - !this.isLoading) { - this.store.startTimeAgoLoops.call(this, Vue); - } - }, - beforeDestroyed() { eventHub.$off('refreshPipelines'); }, diff --git a/app/assets/javascripts/environments/components/environment.js b/app/assets/javascripts/environments/components/environment.vue index f7175e412da..f319d6ca0c8 100644 --- a/app/assets/javascripts/environments/components/environment.js +++ b/app/assets/javascripts/environments/components/environment.vue @@ -1,6 +1,7 @@ +<script> + /* eslint-disable no-new */ /* global Flash */ -import Vue from 'vue'; import EnvironmentsService from '../services/environments_service'; import EnvironmentTable from './environments_table.vue'; import EnvironmentsStore from '../stores/environments_store'; @@ -8,7 +9,7 @@ import TablePaginationComponent from '../../vue_shared/components/table_paginati import '../../lib/utils/common_utils'; import eventHub from '../event_hub'; -export default Vue.component('environment-component', { +export default { components: { 'environment-table': EnvironmentTable, @@ -140,76 +141,90 @@ export default Vue.component('environment-component', { }); }, }, - - template: ` - <div :class="cssContainerClass"> - <div class="top-area"> - <ul v-if="!isLoading" class="nav-links"> - <li v-bind:class="{ 'active': scope === null || scope === 'available' }"> - <a :href="projectEnvironmentsPath"> - Available - <span class="badge js-available-environments-count"> - {{state.availableCounter}} - </span> - </a> - </li> - <li v-bind:class="{ 'active' : scope === 'stopped' }"> - <a :href="projectStoppedEnvironmentsPath"> - Stopped - <span class="badge js-stopped-environments-count"> - {{state.stoppedCounter}} - </span> - </a> - </li> - </ul> - <div v-if="canCreateEnvironmentParsed && !isLoading" class="nav-controls"> - <a :href="newEnvironmentPath" class="btn btn-create"> - New environment +}; +</script> +<template> + <div :class="cssContainerClass"> + <div class="top-area"> + <ul + v-if="!isLoading" + class="nav-links"> + <li :class="{ active: scope === null || scope === 'available' }"> + <a :href="projectEnvironmentsPath"> + Available + <span class="badge js-available-environments-count"> + {{state.availableCounter}} + </span> + </a> + </li> + <li :class="{ active : scope === 'stopped' }"> + <a :href="projectStoppedEnvironmentsPath"> + Stopped + <span class="badge js-stopped-environments-count"> + {{state.stoppedCounter}} + </span> </a> - </div> + </li> + </ul> + <div + v-if="canCreateEnvironmentParsed && !isLoading" + class="nav-controls"> + <a + :href="newEnvironmentPath" + class="btn btn-create"> + New environment + </a> </div> + </div> + + <div class="content-list environments-container"> + <div + class="environments-list-loading text-center" + v-if="isLoading"> - <div class="content-list environments-container"> - <div class="environments-list-loading text-center" v-if="isLoading"> - <i class="fa fa-spinner fa-spin" aria-hidden="true"></i> - </div> - - <div class="blank-state blank-state-no-icon" - v-if="!isLoading && state.environments.length === 0"> - <h2 class="blank-state-title js-blank-state-title"> - You don't have any environments right now. - </h2> - <p class="blank-state-text"> - Environments are places where code gets deployed, such as staging or production. - <br /> - <a :href="helpPagePath"> - Read more about environments - </a> - </p> - - <a v-if="canCreateEnvironmentParsed" - :href="newEnvironmentPath" - class="btn btn-create js-new-environment-button"> - New Environment + <i + class="fa fa-spinner fa-spin" + aria-hidden="true" /> + </div> + + <div + class="blank-state blank-state-no-icon" + v-if="!isLoading && state.environments.length === 0"> + <h2 class="blank-state-title js-blank-state-title"> + You don't have any environments right now. + </h2> + <p class="blank-state-text"> + Environments are places where code gets deployed, such as staging or production. + <br /> + <a :href="helpPagePath"> + Read more about environments </a> - </div> - - <div class="table-holder" - v-if="!isLoading && state.environments.length > 0"> - - <environment-table - :environments="state.environments" - :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed" - :service="service" - :is-loading-folder-content="isLoadingFolderContent" /> - </div> - - <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" - :change="changePage" - :pageInfo="state.paginationInformation"> - </table-pagination> + </p> + + <a + v-if="canCreateEnvironmentParsed" + :href="newEnvironmentPath" + class="btn btn-create js-new-environment-button"> + New Environment + </a> </div> + + <div + class="table-holder" + v-if="!isLoading && state.environments.length > 0"> + + <environment-table + :environments="state.environments" + :can-create-deployment="canCreateDeploymentParsed" + :can-read-environment="canReadEnvironmentParsed" + :service="service" + :is-loading-folder-content="isLoadingFolderContent" /> + </div> + + <table-pagination + v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" + :change="changePage" + :pageInfo="state.paginationInformation" /> </div> - `, -}); + </div> +</template> diff --git a/app/assets/javascripts/environments/environments_bundle.js b/app/assets/javascripts/environments/environments_bundle.js index 8d963b335cf..c0662125f28 100644 --- a/app/assets/javascripts/environments/environments_bundle.js +++ b/app/assets/javascripts/environments/environments_bundle.js @@ -1,13 +1,10 @@ -import EnvironmentsComponent from './components/environment'; +import Vue from 'vue'; +import EnvironmentsComponent from './components/environment.vue'; -$(() => { - window.gl = window.gl || {}; - - if (gl.EnvironmentsListApp) { - gl.EnvironmentsListApp.$destroy(true); - } - - gl.EnvironmentsListApp = new EnvironmentsComponent({ - el: document.querySelector('#environments-list-view'), - }); -}); +document.addEventListener('DOMContentLoaded', () => new Vue({ + el: '#environments-list-view', + components: { + 'environments-table-app': EnvironmentsComponent, + }, + render: createElement => createElement('environments-table-app'), +})); diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index f939eccf246..9add8c3d721 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -1,13 +1,10 @@ -import EnvironmentsFolderComponent from './environments_folder_view'; +import Vue from 'vue'; +import EnvironmentsFolderComponent from './environments_folder_view.vue'; -$(() => { - window.gl = window.gl || {}; - - if (gl.EnvironmentsListFolderApp) { - gl.EnvironmentsListFolderApp.$destroy(true); - } - - gl.EnvironmentsListFolderApp = new EnvironmentsFolderComponent({ - el: document.querySelector('#environments-folder-list-view'), - }); -}); +document.addEventListener('DOMContentLoaded', () => new Vue({ + el: '#environments-folder-list-view', + components: { + 'environments-folder-app': EnvironmentsFolderComponent, + }, + render: createElement => createElement('environments-folder-app'), +})); diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 05d44f77d1d..d27b2acfcdf 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.js +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,6 +1,6 @@ +<script> /* eslint-disable no-new */ /* global Flash */ -import Vue from 'vue'; import EnvironmentsService from '../services/environments_service'; import EnvironmentTable from '../components/environments_table.vue'; import EnvironmentsStore from '../stores/environments_store'; @@ -8,7 +8,7 @@ import TablePaginationComponent from '../../vue_shared/components/table_paginati import '../../lib/utils/common_utils'; import '../../vue_shared/vue_resource_interceptor'; -export default Vue.component('environment-folder-view', { +export default { components: { 'environment-table': EnvironmentTable, 'table-pagination': TablePaginationComponent, @@ -116,54 +116,66 @@ export default Vue.component('environment-folder-view', { return param; }, }, +}; +</script> +<template> + <div :class="cssContainerClass"> + <div + class="top-area" + v-if="!isLoading"> + + <h4 class="js-folder-name environments-folder-name"> + Environments / <b>{{folderName}}</b> + </h4> + + <ul class="nav-links"> + <li :class="{ active: scope === null || scope === 'available' }"> + <a + :href="availablePath" + class="js-available-environments-folder-tab"> + Available + <span class="badge js-available-environments-count"> + {{state.availableCounter}} + </span> + </a> + </li> + <li :class="{ active : scope === 'stopped' }"> + <a + :href="stoppedPath" + class="js-stopped-environments-folder-tab"> + Stopped + <span class="badge js-stopped-environments-count"> + {{state.stoppedCounter}} + </span> + </a> + </li> + </ul> + </div> - template: ` - <div :class="cssContainerClass"> - <div class="top-area" v-if="!isLoading"> - - <h4 class="js-folder-name environments-folder-name"> - Environments / <b>{{folderName}}</b> - </h4> - - <ul class="nav-links"> - <li v-bind:class="{ 'active': scope === null || scope === 'available' }"> - <a :href="availablePath" class="js-available-environments-folder-tab"> - Available - <span class="badge js-available-environments-count"> - {{state.availableCounter}} - </span> - </a> - </li> - <li v-bind:class="{ 'active' : scope === 'stopped' }"> - <a :href="stoppedPath" class="js-stopped-environments-folder-tab"> - Stopped - <span class="badge js-stopped-environments-count"> - {{state.stoppedCounter}} - </span> - </a> - </li> - </ul> + <div class="environments-container"> + <div + class="environments-list-loading text-center" + v-if="isLoading"> + <i + class="fa fa-spinner fa-spin" + aria-hidden="true"/> </div> - <div class="environments-container"> - <div class="environments-list-loading text-center" v-if="isLoading"> - <i class="fa fa-spinner fa-spin"></i> - </div> - - <div class="table-holder" - v-if="!isLoading && state.environments.length > 0"> + <div + class="table-holder" + v-if="!isLoading && state.environments.length > 0"> - <environment-table - :environments="state.environments" - :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed" - :service="service"/> + <environment-table + :environments="state.environments" + :can-create-deployment="canCreateDeploymentParsed" + :can-read-environment="canReadEnvironmentParsed" + :service="service"/> - <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" - :change="changePage" - :pageInfo="state.paginationInformation"/> - </div> + <table-pagination + v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" + :change="changePage" + :pageInfo="state.paginationInformation"/> </div> </div> - `, -}); + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/time_ago.js b/app/assets/javascripts/pipelines/components/time_ago.js index 498d0715f54..188f74cc705 100644 --- a/app/assets/javascripts/pipelines/components/time_ago.js +++ b/app/assets/javascripts/pipelines/components/time_ago.js @@ -2,68 +2,95 @@ import iconTimerSvg from 'icons/_icon_timer.svg'; import '../../lib/utils/datetime_utility'; export default { + props: { + finishedTime: { + type: String, + required: true, + }, + + duration: { + type: Number, + required: true, + }, + }, + data() { return { - currentTime: new Date(), iconTimerSvg, }; }, - props: ['pipeline'], + + updated() { + $(this.$refs.tooltip).tooltip('fixTitle'); + }, + computed: { - timeAgo() { - return gl.utils.getTimeago(); + hasDuration() { + return this.duration > 0; }, - localTimeFinished() { - return gl.utils.formatDate(this.pipeline.details.finished_at); + + hasFinishedTime() { + return this.finishedTime !== ''; }, - timeStopped() { - const changeTime = this.currentTime; - const options = { - weekday: 'long', - year: 'numeric', - month: 'short', - day: 'numeric', - }; - options.timeZoneName = 'short'; - const finished = this.pipeline.details.finished_at; - if (!finished && changeTime) return false; - return ({ words: this.timeAgo.format(finished) }); + + localTimeFinished() { + return gl.utils.formatDate(this.finishedTime); }, - duration() { - const { duration } = this.pipeline.details; - const date = new Date(duration * 1000); + + durationFormated() { + const date = new Date(this.duration * 1000); let hh = date.getUTCHours(); let mm = date.getUTCMinutes(); let ss = date.getSeconds(); - if (hh < 10) hh = `0${hh}`; - if (mm < 10) mm = `0${mm}`; - if (ss < 10) ss = `0${ss}`; + // left pad + if (hh < 10) { + hh = `0${hh}`; + } + if (mm < 10) { + mm = `0${mm}`; + } + if (ss < 10) { + ss = `0${ss}`; + } - if (duration !== null) return `${hh}:${mm}:${ss}`; - return false; + return `${hh}:${mm}:${ss}`; }, - }, - methods: { - changeTime() { - this.currentTime = new Date(); + + finishedTimeFormated() { + const timeAgo = gl.utils.getTimeago(); + + return timeAgo.format(this.finishedTime); }, }, + template: ` <td class="pipelines-time-ago"> - <p class="duration" v-if='duration'> - <span v-html="iconTimerSvg"></span> - {{duration}} + <p + class="duration" + v-if="hasDuration"> + <span + v-html="iconTimerSvg"> + </span> + {{durationFormated}} </p> - <p class="finished-at" v-if='timeStopped'> - <i class="fa fa-calendar"></i> + + <p + class="finished-at" + v-if="hasFinishedTime"> + + <i + class="fa fa-calendar" + aria-hidden="true" /> + <time + ref="tooltip" data-toggle="tooltip" data-placement="top" data-container="body" - :data-original-title='localTimeFinished'> - {{timeStopped.words}} + :title="localTimeFinished"> + {{finishedTimeFormated}} </time> </p> </td> diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js index 6eea4812f33..93d4818231f 100644 --- a/app/assets/javascripts/pipelines/pipelines.js +++ b/app/assets/javascripts/pipelines/pipelines.js @@ -1,4 +1,3 @@ -import Vue from 'vue'; import Visibility from 'visibilityjs'; import PipelinesService from './services/pipelines_service'; import eventHub from './event_hub'; @@ -161,15 +160,6 @@ export default { eventHub.$on('refreshPipelines', this.fetchPipelines); }, - beforeUpdate() { - if (this.state.pipelines.length && - this.$children && - !this.isMakingRequest && - !this.isLoading) { - this.store.startTimeAgoLoops.call(this, Vue); - } - }, - beforeDestroyed() { eventHub.$off('refreshPipelines'); }, diff --git a/app/assets/javascripts/pipelines/stores/pipelines_store.js b/app/assets/javascripts/pipelines/stores/pipelines_store.js index 377ec8ba2cc..ffefe0192f2 100644 --- a/app/assets/javascripts/pipelines/stores/pipelines_store.js +++ b/app/assets/javascripts/pipelines/stores/pipelines_store.js @@ -1,6 +1,3 @@ -/* eslint-disable no-underscore-dangle*/ -import VueRealtimeListener from '../../vue_realtime_listener'; - export default class PipelinesStore { constructor() { this.state = {}; @@ -30,32 +27,4 @@ export default class PipelinesStore { this.state.pageInfo = paginationInfo; } - - /** - * FIXME: Move this inside the component. - * - * Once the data is received we will start the time ago loops. - * - * Everytime a request is made like retry or cancel a pipeline, every 10 seconds we - * update the time to show how long as passed. - * - */ - startTimeAgoLoops() { - const startTimeLoops = () => { - this.timeLoopInterval = setInterval(() => { - this.$children[0].$children.reduce((acc, component) => { - const timeAgoComponent = component.$children.filter(el => el.$options._componentTag === 'time-ago')[0]; - acc.push(timeAgoComponent); - return acc; - }, []).forEach(e => e.changeTime()); - }, 10000); - }; - - startTimeLoops(); - - const removeIntervals = () => clearInterval(this.timeLoopInterval); - const startIntervals = () => startTimeLoops(); - - VueRealtimeListener(removeIntervals, startIntervals); - } } diff --git a/app/assets/javascripts/vue_realtime_listener/index.js b/app/assets/javascripts/vue_realtime_listener/index.js deleted file mode 100644 index 4ddb2f975b0..00000000000 --- a/app/assets/javascripts/vue_realtime_listener/index.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (removeIntervals, startIntervals) => { - window.removeEventListener('focus', startIntervals); - window.removeEventListener('blur', removeIntervals); - window.removeEventListener('onbeforeload', removeIntervals); - - window.addEventListener('focus', startIntervals); - window.addEventListener('blur', removeIntervals); - window.addEventListener('onbeforeload', removeIntervals); -}; diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js index 62b7131de51..79806bc7204 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js @@ -1,5 +1,4 @@ /* eslint-disable no-param-reassign */ - import AsyncButtonComponent from '../../pipelines/components/async_button.vue'; import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions'; import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts'; @@ -166,6 +165,32 @@ export default { } return undefined; }, + + /** + * Timeago components expects a number + * + * @return {type} description + */ + pipelineDuration() { + if (this.pipeline.details && this.pipeline.details.duration) { + return this.pipeline.details.duration; + } + + return 0; + }, + + /** + * Timeago component expects a String. + * + * @return {String} + */ + pipelineFinishedAt() { + if (this.pipeline.details && this.pipeline.details.finished_at) { + return this.pipeline.details.finished_at; + } + + return ''; + }, }, template: ` @@ -192,7 +217,9 @@ export default { </div> </td> - <time-ago :pipeline="pipeline"/> + <time-ago + :duration="pipelineDuration" + :finished-time="pipelineFinishedAt" /> <td class="pipeline-actions"> <div class="pull-right btn-group"> diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e5b811f3300..fff57472a4f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -196,38 +196,6 @@ module ApplicationHelper end end - def render_markup(file_name, file_content) - if gitlab_markdown?(file_name) - Hamlit::RailsHelpers.preserve(markdown(file_content)) - elsif asciidoc?(file_name) - asciidoc(file_content) - elsif plain?(file_name) - content_tag :pre, class: 'plain-readme' do - file_content - end - else - other_markup(file_name, file_content) - end - rescue RuntimeError - simple_format(file_content) - end - - def plain?(filename) - Gitlab::MarkupHelper.plain?(filename) - end - - def markup?(filename) - Gitlab::MarkupHelper.markup?(filename) - end - - def gitlab_markdown?(filename) - Gitlab::MarkupHelper.gitlab_markdown?(filename) - end - - def asciidoc?(filename) - Gitlab::MarkupHelper.asciidoc?(filename) - end - def promo_host 'about.gitlab.com' end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/markup_helper.rb index 106feb87398..0781874d7fc 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/markup_helper.rb @@ -1,6 +1,22 @@ require 'nokogiri' -module GitlabMarkdownHelper +module MarkupHelper + def plain?(filename) + Gitlab::MarkupHelper.plain?(filename) + end + + def markup?(filename) + Gitlab::MarkupHelper.markup?(filename) + end + + def gitlab_markdown?(filename) + Gitlab::MarkupHelper.gitlab_markdown?(filename) + end + + def asciidoc?(filename) + Gitlab::MarkupHelper.asciidoc?(filename) + end + # Use this in places where you would normally use link_to(gfm(...), ...). # # It solves a problem occurring with nested links (i.e. @@ -11,7 +27,7 @@ module GitlabMarkdownHelper # explicitly produce the correct linking behavior (i.e. # "<a>outer text </a><a>gfm ref</a><a> more outer text</a>"). def link_to_gfm(body, url, html_options = {}) - return "" if body.blank? + return '' if body.blank? context = { project: @project, @@ -43,71 +59,73 @@ module GitlabMarkdownHelper fragment.to_html.html_safe end + # Return the first line of +text+, up to +max_chars+, after parsing the line + # as Markdown. HTML tags in the parsed output are not counted toward the + # +max_chars+ limit. If the length limit falls within a tag's contents, then + # the tag contents are truncated without removing the closing tag. + def first_line_in_markdown(text, max_chars = nil, options = {}) + md = markdown(text, options).strip + + truncate_visible(md, max_chars || md.length) if md.present? + end + def markdown(text, context = {}) - return "" unless text.present? + return '' unless text.present? context[:project] ||= @project - - html = Banzai.render(text, context) + html = markdown_unsafe(text, context) banzai_postprocess(html, context) end def markdown_field(object, field) object = object.for_display if object.respond_to?(:for_display) - return "" unless object.present? + return '' unless object.present? html = Banzai.render_field(object, field) banzai_postprocess(html, object.banzai_render_context(field)) end - def asciidoc(text) - Gitlab::Asciidoc.render( - text, - project: @project, - current_user: (current_user if defined?(current_user)), - - # RelativeLinkFilter - project_wiki: @project_wiki, - requested_path: @path, - ref: @ref, - commit: @commit - ) + def markup(file_name, text, context = {}) + context[:project] ||= @project + html = context.delete(:rendered) || markup_unsafe(file_name, text, context) + banzai_postprocess(html, context) end - def other_markup(file_name, text) - Gitlab::OtherMarkup.render( - file_name, - text, - project: @project, - current_user: (current_user if defined?(current_user)), + def render_wiki_content(wiki_page) + text = wiki_page.content + return '' unless text.present? + + context = { pipeline: :wiki, project: @project, project_wiki: @project_wiki, page_slug: wiki_page.slug } + + html = + case wiki_page.format + when :markdown + markdown_unsafe(text, context) + when :asciidoc + asciidoc_unsafe(text) + else + wiki_page.formatted_content.html_safe + end - # RelativeLinkFilter - project_wiki: @project_wiki, - requested_path: @path, - ref: @ref, - commit: @commit - ) + banzai_postprocess(html, context) end - # Return the first line of +text+, up to +max_chars+, after parsing the line - # as Markdown. HTML tags in the parsed output are not counted toward the - # +max_chars+ limit. If the length limit falls within a tag's contents, then - # the tag contents are truncated without removing the closing tag. - def first_line_in_markdown(text, max_chars = nil, options = {}) - md = markdown(text, options).strip + def markup_unsafe(file_name, text, context = {}) + return '' unless text.present? - truncate_visible(md, max_chars || md.length) if md.present? - end - - def render_wiki_content(wiki_page) - case wiki_page.format - when :markdown - markdown(wiki_page.content, pipeline: :wiki, project_wiki: @project_wiki, page_slug: wiki_page.slug) - when :asciidoc - asciidoc(wiki_page.content) + if gitlab_markdown?(file_name) + Hamlit::RailsHelpers.preserve(markdown_unsafe(text, context)) + elsif asciidoc?(file_name) + asciidoc_unsafe(text) + elsif plain?(file_name) + content_tag :pre, class: 'plain-readme' do + text + end else - wiki_page.formatted_content.html_safe + other_markup_unsafe(file_name, text) end + rescue RuntimeError + simple_format(text) end # Returns the text necessary to reference `entity` across projects @@ -183,10 +201,10 @@ module GitlabMarkdownHelper end def markdown_toolbar_button(options = {}) - data = options[:data].merge({ container: "body" }) + data = options[:data].merge({ container: 'body' }) content_tag :button, - type: "button", - class: "toolbar-btn js-md has-tooltip hidden-xs", + type: 'button', + class: 'toolbar-btn js-md has-tooltip hidden-xs', tabindex: -1, data: data, title: options[:title], @@ -195,17 +213,34 @@ module GitlabMarkdownHelper end end + def markdown_unsafe(text, context = {}) + Banzai.render(text, context) + end + + def asciidoc_unsafe(text) + Gitlab::Asciidoc.render(text) + end + + def other_markup_unsafe(file_name, text) + Gitlab::OtherMarkup.render(file_name, text) + end + # Calls Banzai.post_process with some common context options def banzai_postprocess(html, context = {}) + return '' unless html.present? + context.merge!( current_user: (current_user if defined?(current_user)), # RelativeLinkFilter - requested_path: @path, + commit: @commit, project_wiki: @project_wiki, - ref: @ref + ref: @ref, + requested_path: @path ) Banzai.post_process(html, context) end + + extend self end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index f1dab60524e..f7b5a5f4dfc 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -12,10 +12,6 @@ module TreeHelper tree.html_safe end - def render_readme(readme) - render_markup(readme.name, readme.data) - end - # Return an image icon depending on the file type and mode # # type - String type of the tree item; either 'folder' or 'file' diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index a9b6b33eb5c..d2980db218a 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -1,6 +1,6 @@ class BaseMailer < ActionMailer::Base helper ApplicationHelper - helper GitlabMarkdownHelper + helper MarkupHelper attr_accessor :current_user helper_method :current_user, :can? diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index dd1a6922968..cf042717c95 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -28,6 +28,8 @@ class ApplicationSetting < ActiveRecord::Base attr_accessor :domain_whitelist_raw, :domain_blacklist_raw + validates :uuid, presence: true + validates :session_expire_delay, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } @@ -159,6 +161,7 @@ class ApplicationSetting < ActiveRecord::Base end end + before_validation :ensure_uuid! before_save :ensure_runners_registration_token before_save :ensure_health_check_access_token @@ -344,6 +347,12 @@ class ApplicationSetting < ActiveRecord::Base private + def ensure_uuid! + return if uuid? + + self.uuid = SecureRandom.uuid + end + def check_repository_storages invalid = repository_storages - Gitlab.config.repositories.storages.keys errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 0bbc9451ffd..59737bb6085 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -107,7 +107,8 @@ module Network def find_commits(skip = 0) opts = { max_count: self.class.max_count, - skip: skip + skip: skip, + order: :date } opts[:ref] = @commit.id if @filter_ref diff --git a/app/models/repository.rb b/app/models/repository.rb index 7bb874d7744..e74edb8e6f7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -17,9 +17,9 @@ class Repository # same name. The cache key used by those methods must also match method's # name. # - # For example, for entry `:readme` there's a method called `readme` which - # stores its data in the `readme` cache key. - CACHED_METHODS = %i(size commit_count readme contribution_guide + # For example, for entry `:commit_count` there's a method called `commit_count` which + # stores its data in the `commit_count` cache key. + CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide changelog license_blob license_key gitignore koding_yml gitlab_ci_yml branch_names tag_names branch_count tag_count avatar exists? empty? root_ref).freeze @@ -28,7 +28,7 @@ class Repository # changed. This Hash maps file types (as returned by Gitlab::FileDetector) to # the corresponding methods to call for refreshing caches. METHOD_CACHES_FOR_FILE_TYPES = { - readme: :readme, + readme: :rendered_readme, changelog: :changelog, license: %i(license_blob license_key), contributing: :contribution_guide, @@ -527,7 +527,11 @@ class Repository head.readme end end - cache_method :readme + + def rendered_readme + MarkupHelper.markup_unsafe(readme.name, readme.data, project: project) if readme + end + cache_method :rendered_readme def contribution_guide file_on_head(:contributing) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 7828c5806b0..535d93385e6 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -97,7 +97,8 @@ module Projects system_hook_service.execute_hooks_for(@project, :create) unless @project.group || @project.gitlab_project_import? - @project.team << [current_user, :master, current_user] + owners = [current_user, @project.namespace.owner].compact.uniq + @project.add_master(owners, current_user: current_user) end @project.group&.refresh_members_authorized_projects diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index b6fb08b68e9..c0d12cbc66e 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -4,8 +4,7 @@ - if can?(current_user, :push_code, @project) = link_to icon('pencil'), namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light edit-project-readme' .file-content.wiki - = cache(readme_cache_key) do - = render_readme(readme) + = markup(readme.name, readme.data, rendered: @repository.rendered_readme) - else .row-content-block.second-block.center %h3.page-title diff --git a/app/views/projects/blob/_markup.html.haml b/app/views/projects/blob/_markup.html.haml index 4ee4b03ff04..0090f7a11df 100644 --- a/app/views/projects/blob/_markup.html.haml +++ b/app/views/projects/blob/_markup.html.haml @@ -1,4 +1,4 @@ - blob.load_all_data!(@repository) .file-content.wiki - = render_markup(blob.name, blob.data) + = markup(blob.name, blob.data) diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml index 5cafb644b40..e87b73c9a34 100644 --- a/app/views/projects/blob/preview.html.haml +++ b/app/views/projects/blob/preview.html.haml @@ -1,12 +1,8 @@ .diff-file .diff-content - - if gitlab_markdown?(@blob.name) + - if markup?(@blob.name) .file-content.wiki - = preserve do - = markdown(@content) - - elsif markup?(@blob.name) - .file-content.wiki - = raw render_markup(@blob.name, @content) + = markup(@blob.name, @content) - else .file-content.code.js-syntax-highlight - unless @diff_lines.empty? diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index bdcc160a067..01599060844 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -5,4 +5,4 @@ %strong = readme.name .file-content.wiki - = render_readme(readme) + = markup(readme.name, readme.data) diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index f84be600df8..f2fe5742c12 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -21,7 +21,7 @@ .file-content.wiki - snippet_chunks.each do |chunk| - unless chunk[:data].empty? - = render_markup(snippet.file_name, chunk[:data]) + = markup(snippet.file_name, chunk[:data]) - else .file-content.code .nothing-here-block Empty file diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml index 74f71e6cbd1..895c3f1e99d 100644 --- a/app/views/shared/snippets/_blob.html.haml +++ b/app/views/shared/snippets/_blob.html.haml @@ -24,6 +24,6 @@ - if gitlab_markdown?(@snippet.file_name) = preserve(markdown_field(@snippet, :content)) - else - = render_markup(@snippet.file_name, @snippet.content) + = markup(@snippet.file_name, @snippet.content) - else = render 'shared/file_highlight', blob: @snippet diff --git a/changelogs/unreleased/2246-uuid-is-nil-for-new-installation.yml b/changelogs/unreleased/2246-uuid-is-nil-for-new-installation.yml new file mode 100644 index 00000000000..70d35f06af4 --- /dev/null +++ b/changelogs/unreleased/2246-uuid-is-nil-for-new-installation.yml @@ -0,0 +1,4 @@ +--- +title: Lazily sets UUID in ApplicationSetting for new installations +merge_request: +author: diff --git a/changelogs/unreleased/26585-remove-readme-view-caching.yml b/changelogs/unreleased/26585-remove-readme-view-caching.yml new file mode 100644 index 00000000000..6aefae982bf --- /dev/null +++ b/changelogs/unreleased/26585-remove-readme-view-caching.yml @@ -0,0 +1,4 @@ +--- +title: 'Remove view fragment caching for project READMEs' +merge_request: 8838 +author: diff --git a/changelogs/unreleased/29505-allow-admins-sudo-to-blocked-users.yml b/changelogs/unreleased/29505-allow-admins-sudo-to-blocked-users.yml new file mode 100644 index 00000000000..42fd71ccd5f --- /dev/null +++ b/changelogs/unreleased/29505-allow-admins-sudo-to-blocked-users.yml @@ -0,0 +1,4 @@ +--- +title: Allow admins to sudo to blocked users via the API +merge_request: 10842 +author: diff --git a/changelogs/unreleased/30973-fix-network-graph-ordering.yml b/changelogs/unreleased/30973-fix-network-graph-ordering.yml new file mode 100644 index 00000000000..420ec107842 --- /dev/null +++ b/changelogs/unreleased/30973-fix-network-graph-ordering.yml @@ -0,0 +1,4 @@ +--- +title: Fix ordering of commits in the network graph +merge_request: 10936 +author: diff --git a/changelogs/unreleased/tc-make-user-master-project-by-admin.yml b/changelogs/unreleased/tc-make-user-master-project-by-admin.yml new file mode 100644 index 00000000000..459d6178bdd --- /dev/null +++ b/changelogs/unreleased/tc-make-user-master-project-by-admin.yml @@ -0,0 +1,4 @@ +--- +title: Ensure namespace owner is Master of project upon creation +merge_request: 10910 +author: diff --git a/db/migrate/20170426175636_fill_missing_uuid_on_application_settings.rb b/db/migrate/20170426175636_fill_missing_uuid_on_application_settings.rb new file mode 100644 index 00000000000..58ad2c64075 --- /dev/null +++ b/db/migrate/20170426175636_fill_missing_uuid_on_application_settings.rb @@ -0,0 +1,10 @@ +class FillMissingUuidOnApplicationSettings < ActiveRecord::Migration + DOWNTIME = false + + def up + execute("UPDATE application_settings SET uuid = #{quote(SecureRandom.uuid)} WHERE uuid is NULL") + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index ff00951d5f6..49d7c996661 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170424142900) do +ActiveRecord::Schema.define(version: 20170426175636) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index ddff3c8c1e8..86bf567fe69 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -102,7 +102,7 @@ module API end def authenticate! - unauthorized! unless current_user && can?(current_user, :access_api) + unauthorized! unless current_user && can?(initial_current_user, :access_api) end def authenticate_non_get! diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index d575367d81a..fba80c7132e 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -14,28 +14,16 @@ module Gitlab # Public: Converts the provided Asciidoc markup into HTML. # # input - the source text in Asciidoc format - # context - a Hash with the template context: - # :commit - # :project - # :project_wiki - # :requested_path - # :ref - # asciidoc_opts - a Hash of options to pass to the Asciidoctor converter # - def self.render(input, context, asciidoc_opts = {}) - asciidoc_opts.reverse_merge!( - safe: :secure, - backend: :gitlab_html5, - attributes: [] - ) - asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS) + def self.render(input) + asciidoc_opts = { safe: :secure, + backend: :gitlab_html5, + attributes: DEFAULT_ADOC_ATTRS } plantuml_setup html = ::Asciidoctor.convert(input, asciidoc_opts) - html = Banzai.post_process(html, context) - filter = Banzai::Filter::SanitizationFilter.new(html) html = filter.call.to_s diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index d7dac9f6149..452dba7971d 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -494,7 +494,9 @@ module Gitlab # :contains is the commit contained by the refs from which to begin (SHA1 or name) # :max_count is the maximum number of commits to fetch # :skip is the number of commits to skip - # :order is the commits order and allowed value is :date(default) or :topo + # :order is the commits order and allowed value is :none (default), :date, or :topo + # commit ordering types are documented here: + # http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant) # def find_commits(options = {}) actual_options = options.dup @@ -522,11 +524,8 @@ module Gitlab end end - if actual_options[:order] == :topo - walker.sorting(Rugged::SORT_TOPO) - else - walker.sorting(Rugged::SORT_NONE) - end + sort_type = rugged_sort_type(actual_options[:order]) + walker.sorting(sort_type) commits = [] offset = actual_options[:skip] @@ -1273,6 +1272,18 @@ module Gitlab def gitaly_ref_client @gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self) end + + # Returns the `Rugged` sorting type constant for a given + # sort type key. Valid keys are `:none`, `:topo`, and `:date` + def rugged_sort_type(key) + @rugged_sort_types ||= { + none: Rugged::SORT_NONE, + topo: Rugged::SORT_TOPO, + date: Rugged::SORT_DATE + } + + @rugged_sort_types.fetch(key, Rugged::SORT_NONE) + end end end end diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb index e67acf28c94..c2adc9aa10b 100644 --- a/lib/gitlab/other_markup.rb +++ b/lib/gitlab/other_markup.rb @@ -4,19 +4,11 @@ module Gitlab # Public: Converts the provided markup into HTML. # # input - the source text in a markup format - # context - a Hash with the template context: - # :commit - # :project - # :project_wiki - # :requested_path - # :ref # - def self.render(file_name, input, context) + def self.render(file_name, input) html = GitHub::Markup.render(file_name, input). force_encoding(input.encoding) - html = Banzai.post_process(html, context) - filter = Banzai::Filter::SanitizationFilter.new(html) html = filter.call.to_s diff --git a/spec/features/admin/admin_cohorts_spec.rb b/spec/features/admin/admin_cohorts_spec.rb new file mode 100644 index 00000000000..dd14ffdb2ce --- /dev/null +++ b/spec/features/admin/admin_cohorts_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +feature 'Admin cohorts page', feature: true do + before do + login_as :admin + end + + scenario 'See users count per month' do + 2.times { create(:user) } + + visit admin_cohorts_path + + expect(page).to have_content("#{Time.now.strftime('%b %Y')} 3 0") + end +end diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index 64a6c70061b..344e31e5ef5 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'Copy as GFM', feature: true, js: true do - include GitlabMarkdownHelper + include MarkupHelper include RepoHelpers include ActionView::Helpers::JavaScriptHelper diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 894df13a2dc..ba930de937d 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -26,7 +26,7 @@ require 'erb' describe 'GitLab Markdown', feature: true do include Capybara::Node::Matchers - include GitlabMarkdownHelper + include MarkupHelper include MarkdownMatchers # Sometimes it can be useful to see the parsed output of the Markdown document diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 5c07ea8a872..01bdf01ad22 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -239,33 +239,6 @@ describe ApplicationHelper do end end - describe 'render_markup' do - let(:content) { 'Noël' } - let(:user) { create(:user) } - before do - allow(helper).to receive(:current_user).and_return(user) - end - - it 'preserves encoding' do - expect(content.encoding.name).to eq('UTF-8') - expect(helper.render_markup('foo.rst', content).encoding.name).to eq('UTF-8') - end - - it "delegates to #markdown when file name corresponds to Markdown" do - expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true) - expect(helper).to receive(:markdown).and_return('NOEL') - - expect(helper.render_markup('foo.md', content)).to eq('NOEL') - end - - it "delegates to #asciidoc when file name corresponds to AsciiDoc" do - expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true) - expect(helper).to receive(:asciidoc).and_return('NOEL') - - expect(helper.render_markup('foo.adoc', content)).to eq('NOEL') - end - end - describe '#active_when' do it { expect(helper.active_when(true)).to eq('active') } it { expect(helper.active_when(false)).to eq(nil) } diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 2b6cec38b9a..2a0de0b0656 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' -describe GitlabMarkdownHelper do - include ApplicationHelper - +describe MarkupHelper do let!(:project) { create(:project, :repository) } let(:user) { create(:user, username: 'gfm') } @@ -128,7 +126,7 @@ describe GitlabMarkdownHelper do it "uses Wiki pipeline for markdown files" do allow(@wiki).to receive(:format).and_return(:markdown) - expect(helper).to receive(:markdown).with('wiki content', pipeline: :wiki, project_wiki: @wiki, page_slug: "nested/page") + expect(helper).to receive(:markdown_unsafe).with('wiki content', pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page") helper.render_wiki_content(@wiki) end @@ -136,7 +134,7 @@ describe GitlabMarkdownHelper do it "uses Asciidoctor for asciidoc files" do allow(@wiki).to receive(:format).and_return(:asciidoc) - expect(helper).to receive(:asciidoc).with('wiki content') + expect(helper).to receive(:asciidoc_unsafe).with('wiki content') helper.render_wiki_content(@wiki) end @@ -151,6 +149,29 @@ describe GitlabMarkdownHelper do end end + describe 'markup' do + let(:content) { 'Noël' } + + it 'preserves encoding' do + expect(content.encoding.name).to eq('UTF-8') + expect(helper.markup('foo.rst', content).encoding.name).to eq('UTF-8') + end + + it "delegates to #markdown_unsafe when file name corresponds to Markdown" do + expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true) + expect(helper).to receive(:markdown_unsafe).and_return('NOEL') + + expect(helper.markup('foo.md', content)).to eq('NOEL') + end + + it "delegates to #asciidoc_unsafe when file name corresponds to AsciiDoc" do + expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true) + expect(helper).to receive(:asciidoc_unsafe).and_return('NOEL') + + expect(helper.markup('foo.adoc', content)).to eq('NOEL') + end + end + describe '#first_line_in_markdown' do it 'truncates Markdown properly' do text = "@#{user.username}, can you look at this?\nHello world\n" diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js index 9762688af1a..1c54cc3054c 100644 --- a/spec/javascripts/environments/environment_spec.js +++ b/spec/javascripts/environments/environment_spec.js @@ -1,15 +1,18 @@ import Vue from 'vue'; import '~/flash'; -import EnvironmentsComponent from '~/environments/components/environment'; +import environmentsComponent from '~/environments/components/environment.vue'; import { environment, folder } from './mock_data'; describe('Environment', () => { preloadFixtures('static/environments/environments.html.raw'); + let EnvironmentsComponent; let component; beforeEach(() => { loadFixtures('static/environments/environments.html.raw'); + + EnvironmentsComponent = Vue.extend(environmentsComponent); }); describe('successfull request', () => { diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index 72f3db29a66..350078ad5f5 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -1,13 +1,15 @@ import Vue from 'vue'; import '~/flash'; -import EnvironmentsFolderViewComponent from '~/environments/folder/environments_folder_view'; +import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue'; import { environmentsList } from '../mock_data'; describe('Environments Folder View', () => { preloadFixtures('static/environments/environments_folder_view.html.raw'); + let EnvironmentsFolderViewComponent; beforeEach(() => { loadFixtures('static/environments/environments_folder_view.html.raw'); + EnvironmentsFolderViewComponent = Vue.extend(environmentsFolderViewComponent); window.history.pushState({}, null, 'environments/folders/build'); }); diff --git a/spec/javascripts/pipelines/time_ago_spec.js b/spec/javascripts/pipelines/time_ago_spec.js new file mode 100644 index 00000000000..24581e8c672 --- /dev/null +++ b/spec/javascripts/pipelines/time_ago_spec.js @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import timeAgo from '~/pipelines/components/time_ago'; + +describe('Timeago component', () => { + let TimeAgo; + beforeEach(() => { + TimeAgo = Vue.extend(timeAgo); + }); + + describe('with duration', () => { + it('should render duration and timer svg', () => { + const component = new TimeAgo({ + propsData: { + duration: 10, + finishedTime: '', + }, + }).$mount(); + + expect(component.$el.querySelector('.duration')).toBeDefined(); + expect(component.$el.querySelector('.duration svg')).toBeDefined(); + }); + }); + + describe('without duration', () => { + it('should not render duration and timer svg', () => { + const component = new TimeAgo({ + propsData: { + duration: 0, + finishedTime: '', + }, + }).$mount(); + + expect(component.$el.querySelector('.duration')).toBe(null); + }); + }); + + describe('with finishedTime', () => { + it('should render time and calendar icon', () => { + const component = new TimeAgo({ + propsData: { + duration: 0, + finishedTime: '2017-04-26T12:40:23.277Z', + }, + }).$mount(); + + expect(component.$el.querySelector('.finished-at')).toBeDefined(); + expect(component.$el.querySelector('.finished-at i.fa-calendar')).toBeDefined(); + expect(component.$el.querySelector('.finished-at time')).toBeDefined(); + }); + }); + + describe('without finishedTime', () => { + it('should not render time and calendar icon', () => { + const component = new TimeAgo({ + propsData: { + duration: 0, + finishedTime: '', + }, + }).$mount(); + + expect(component.$el.querySelector('.finished-at')).toBe(null); + }); + }); +}); diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index bca57105d1d..0f47fb2fbd9 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -22,26 +22,9 @@ module Gitlab expect(Asciidoctor).to receive(:convert) .with(input, expected_asciidoc_opts).and_return(html) - expect( render(input, context) ).to eql html + expect(render(input)).to eq(html) end - context "with asciidoc_opts" do - let(:asciidoc_opts) { { safe: :safe, attributes: ['foo'] } } - - it "merges the options with default ones" do - expected_asciidoc_opts = { - safe: :safe, - backend: :gitlab_html5, - attributes: described_class::DEFAULT_ADOC_ATTRS + ['foo'] - } - - expect(Asciidoctor).to receive(:convert) - .with(input, expected_asciidoc_opts).and_return(html) - - render(input, context, asciidoc_opts) - end - end - context "XSS" do links = { 'links' => { @@ -60,7 +43,7 @@ module Gitlab links.each do |name, data| it "does not convert dangerous #{name} into HTML" do - expect(render(data[:input], context)).to eql data[:output] + expect(render(data[:input])).to eq(data[:output]) end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 3d6d7292b42..f88653cb1fe 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1031,6 +1031,35 @@ describe Gitlab::Git::Repository, seed_helper: true do end end + describe '#find_commits' do + it 'should return a return a collection of commits' do + commits = repository.find_commits + + expect(commits).not_to be_empty + expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) + end + + context 'while applying a sort order based on the `order` option' do + it "allows ordering topologically (no parents shown before their children)" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO) + + repository.find_commits(order: :topo) + end + + it "allows ordering by date" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE) + + repository.find_commits(order: :date) + end + + it "applies no sorting by default" do + expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE) + + repository.find_commits + end + end + end + describe '#branches with deleted branch' do before(:each) do ref = double() diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb index 22e80ec46be..d6d53e8586c 100644 --- a/spec/lib/gitlab/other_markup_spec.rb +++ b/spec/lib/gitlab/other_markup_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::OtherMarkup, lib: true do } links.each do |name, data| it "does not convert dangerous #{name} into HTML" do - expect(render(data[:file], data[:input], context)).to eql data[:output] + expect(render(data[:file], data[:input])).to eq(data[:output]) end end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 01ca1584ed2..c2c19c62048 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -4,6 +4,7 @@ describe ApplicationSetting, models: true do let(:setting) { ApplicationSetting.create_from_defaults } it { expect(setting).to be_valid } + it { expect(setting.uuid).to be_present } describe 'validations' do let(:http) { 'http://example.com' } diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index 492c4e01bd8..46b36e11c23 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -9,4 +9,25 @@ describe Network::Graph, models: true do expect(graph.notes).to eq( { note_on_commit.commit_id => 1 } ) end + + describe "#commits" do + let(:graph) { described_class.new(project, 'refs/heads/master', project.repository.commit, nil) } + + it "returns a list of commits" do + commits = graph.commits + + expect(commits).not_to be_empty + expect(commits).to all( be_kind_of(Network::Commit) ) + end + + it "sorts the commits by commit date (descending)" do + # Remove duplicate timestamps because they make it harder to + # assert that the commits are sorted as expected. + commits = graph.commits.uniq(&:date) + sorted_commits = commits.sort_by(&:date).reverse + + expect(commits).not_to be_empty + expect(commits.map(&:id)).to eq(sorted_commits.map(&:id)) + end + end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 74d5ebc6db0..98d0641443e 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1803,9 +1803,9 @@ describe Repository, models: true do describe '#refresh_method_caches' do it 'refreshes the caches of the given types' do expect(repository).to receive(:expire_method_caches). - with(%i(readme license_blob license_key)) + with(%i(rendered_readme license_blob license_key)) - expect(repository).to receive(:readme) + expect(repository).to receive(:rendered_readme) expect(repository).to receive(:license_blob) expect(repository).to receive(:license_key) diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 4845ab1ae1f..06c8eb1d0b7 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -427,6 +427,7 @@ describe API::Helpers do context 'current_user is nil' do before do expect_any_instance_of(self.class).to receive(:current_user).and_return(nil) + allow_any_instance_of(self.class).to receive(:initial_current_user).and_return(nil) end it 'returns a 401 response' do @@ -435,13 +436,38 @@ describe API::Helpers do end context 'current_user is present' do + let(:user) { build(:user) } + before do - expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(User.new) + expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user) + expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user) end it 'does not raise an error' do expect { authenticate! }.not_to raise_error end end + + context 'current_user is blocked' do + let(:user) { build(:user, :blocked) } + + before do + expect_any_instance_of(self.class).to receive(:current_user).at_least(:once).and_return(user) + end + + it 'raises an error' do + expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(user) + + expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}' + end + + it "doesn't raise an error if an admin user is impersonating a blocked user (via sudo)" do + admin_user = build(:user, :admin) + + expect_any_instance_of(self.class).to receive(:initial_current_user).and_return(admin_user) + + expect { authenticate! }.not_to raise_error + end + end end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 7a07ea618c0..033e6ecd18c 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -27,6 +27,22 @@ describe Projects::CreateService, '#execute', services: true do end end + context "admin creates project with other user's namespace_id" do + it 'sets the correct permissions' do + admin = create(:admin) + opts = { + name: 'GitLab', + namespace_id: user.namespace.id + } + project = create_project(admin, opts) + + expect(project).to be_persisted + expect(project.owner).to eq(user) + expect(project.team.masters).to include(user, admin) + expect(project.namespace).to eq(user.namespace) + end + end + context 'group namespace' do let(:group) do create(:group).tap do |group| diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 42d63a9f9ba..75d7caf2508 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -595,7 +595,7 @@ describe SystemNoteService, services: true do end shared_examples 'cross project mentionable' do - include GitlabMarkdownHelper + include MarkupHelper it 'contains cross reference to new noteable' do expect(subject.note).to include cross_project_reference(new_project, new_noteable) |