Commit ef7001c9 authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge branch 'master' into 'balsalmiq-support'

# Conflicts:
#   spec/features/projects/commit/cherry_pick_spec.rb
#   spec/features/projects/environments/environment_spec.rb
parents dc3d778f 6068b863
......@@ -45,6 +45,7 @@ eslint-report.html
/public/uploads.*
/public/uploads/
/shared/artifacts/
/spec/javascripts/fixtures/blob/pdf/
/rails_best_practices_output.html
/tags
/tmp/*
......
......@@ -253,38 +253,32 @@ spinach mysql 9 10: *spinach-knapsack-mysql
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
.exec: &exec
.rake-exec: &rake-exec
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
stage: test
script:
- bundle exec $CI_JOB_NAME
- bundle exec rake $CI_JOB_NAME
rubocop:
static-analysis:
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
stage: test
script:
- bundle exec "rubocop --require rubocop-rspec"
rake haml_lint: *exec
rake scss_lint: *exec
rake config_lint: *exec
rake brakeman: *exec
rake flay: *exec
license_finder: *exec
rake downtime_check:
<<: *exec
- scripts/static-analysis
downtime_check:
<<: *rake-exec
except:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
- /^docs\/*/
rake ee_compat_check:
<<: *exec
ee_compat_check:
<<: *rake-exec
only:
- branches@gitlab-org/gitlab-ce
except:
......@@ -309,12 +303,12 @@ rake ee_compat_check:
script:
- bundle exec rake db:migrate:reset
rake pg db:migrate:reset:
db:migrate:reset pg:
<<: *db-migrate-reset
<<: *use-pg
<<: *except-docs
rake mysql db:migrate:reset:
db:migrate:reset mysql:
<<: *db-migrate-reset
<<: *use-mysql
<<: *except-docs
......@@ -326,12 +320,12 @@ rake mysql db:migrate:reset:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
rake pg db:rollback:
db:rollback pg:
<<: *db-rollback
<<: *use-pg
<<: *except-docs
rake mysql db:rollback:
db:rollback mysql:
<<: *db-rollback
<<: *use-mysql
<<: *except-docs
......@@ -353,17 +347,17 @@ rake mysql db:rollback:
paths:
- log/development.log
rake pg db:seed_fu:
db:seed_fu pg:
<<: *db-seed_fu
<<: *use-pg
<<: *except-docs
rake mysql db:seed_fu:
db:seed_fu mysql:
<<: *db-seed_fu
<<: *use-mysql
<<: *except-docs
rake gitlab:assets:compile:
gitlab:assets:compile:
stage: test
<<: *dedicated-runner
<<: *except-docs
......@@ -383,7 +377,7 @@ rake gitlab:assets:compile:
paths:
- webpack-report/
rake karma:
karma:
cache:
paths:
- vendor/ruby
......@@ -402,16 +396,6 @@ rake karma:
paths:
- coverage-javascript/
docs:check:apilint:
image: "phusion/baseimage"
stage: test
<<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
script:
- scripts/lint-doc.sh
docs:check:links:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
stage: test
......@@ -459,11 +443,11 @@ bundler:audit:
- . scripts/prepare_build.sh
- bundle exec rake db:migrate
migration pg paths:
migration path pg:
<<: *migration-paths
<<: *use-pg
migration mysql paths:
migration path mysql:
<<: *migration-paths
<<: *use-mysql
......@@ -485,14 +469,6 @@ coverage:
- coverage/index.html
- coverage/assets/
lint:javascript:
<<: *dedicated-runner
<<: *except-docs
stage: test
before_script: []
script:
- yarn run eslint
lint:javascript:report:
<<: *dedicated-runner
<<: *except-docs
......@@ -548,8 +524,8 @@ pages:
<<: *dedicated-runner
dependencies:
- coverage
- rake karma
- rake gitlab:assets:compile
- karma
- gitlab:assets:compile
- lint:javascript:report
script:
- mv public/ .public/
......
......@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 9.1.2 (2017-05-01)
- Add index on ci_runners.contacted_at. !10876 (blackst0ne)
- Fix pipeline events description for Slack and Mattermost integration. !10908
- Fixed milestone sidebar showing incorrect number of MRs when collapsed. !10933
- Fix ordering of commits in the network graph. !10936
- Ensure the chat notifications service properly saves the "Notify only default branch" setting. !10959
- Lazily sets UUID in ApplicationSetting for new installations.
- Skip validation when creating internal (ghost, service desk) users.
- Use GitLab Pages v0.4.1.
## 9.1.1 (2017-04-26)
- Add a transaction around move_issues_to_ghost_user. !10465
......
......@@ -85,14 +85,14 @@ gem 'kaminari', '~> 0.17.0'
gem 'hamlit', '~> 2.6.1'
# Files attachments
gem 'carrierwave', '~> 0.11.0'
gem 'carrierwave', '~> 1.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
gem 'fog-aws', '~> 0.9'
gem 'fog-core', '~> 1.40'
gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
......
......@@ -105,12 +105,10 @@ GEM
capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3)
launchy
carrierwave (0.11.2)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
carrierwave (1.0.0)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
mimemagic (>= 0.3.0)
cause (0.1)
charlock_holmes (0.7.3)
chronic (0.10.2)
......@@ -184,7 +182,7 @@ GEM
erubis (2.7.0)
escape_utils (1.1.1)
eventmachine (1.0.8)
excon (0.52.0)
excon (0.55.0)
execjs (2.6.0)
expression_parser (0.9.0)
extlib (0.9.16)
......@@ -210,12 +208,12 @@ GEM
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
fog-aws (0.11.0)
fog-aws (0.13.0)
fog-core (~> 1.38)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
fog-core (1.42.0)
fog-core (1.44.1)
builder
excon (~> 0.49)
formatador (~> 0.2)
......@@ -237,9 +235,9 @@ GEM
fog-json (>= 1.0)
fog-xml (>= 0.1)
ipaddress (>= 0.8)
fog-xml (0.1.2)
fog-xml (0.1.3)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
......@@ -871,7 +869,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.11.0)
carrierwave (~> 1.0)
charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
......@@ -896,7 +894,7 @@ DEPENDENCIES
ffaker (~> 2.4)
flay (~> 2.8.0)
fog-aws (~> 0.9)
fog-core (~> 1.40)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
......
/* eslint-disable no-new */
import Vue from 'vue';
import PDFLab from 'vendor/pdflab';
import workerSrc from 'vendor/pdf.worker';
Vue.use(PDFLab, {
workerSrc,
});
import pdfLab from '../../pdf/index.vue';
export default () => {
const el = document.getElementById('js-pdf-viewer');
......@@ -20,6 +15,9 @@ export default () => {
pdf: el.dataset.endpoint,
};
},
components: {
pdfLab,
},
methods: {
onLoad() {
this.loading = false;
......
......@@ -6,7 +6,7 @@ export default class BlobViewer {
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]');
this.$blobContentHolder = $('#blob-content-holder');
this.$fileHolder = $('.file-holder');
let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type');
......@@ -82,7 +82,7 @@ export default class BlobViewer {
viewer.setAttribute('data-loaded', 'true');
this.$blobContentHolder.trigger('highlight:line');
this.$fileHolder.trigger('highlight:line');
this.toggleCopyButtonState();
});
......
......@@ -44,6 +44,7 @@ import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
......@@ -148,8 +149,19 @@ const ShortcutsBlob = require('./shortcuts_blob');
new ProjectsList();
break;
case 'dashboard:groups:index':
new GroupsList();
break;
case 'explore:groups:index':
new GroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) break;
const exploreGroupsLanding = new Landing(
landingElement,
landingElement.querySelector('.dismiss-button'),
'explore_groups_landing_dismissed',
);
exploreGroupsLanding.toggle();
break;
case 'projects:milestones:new':
case 'projects:milestones:edit':
......@@ -356,6 +368,10 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'users:show':
new UserCallout();
break;
case 'snippets:show':
new LineHighlighter();
new BlobViewer();
break;
}
switch (path.first()) {
case 'sessions':
......@@ -434,6 +450,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') {
new ZenMode();
new LineHighlighter();
new BlobViewer();
}
break;
case 'labels':
......
import Cookies from 'js-cookie';
class Landing {
constructor(landingElement, dismissButton, cookieName) {
this.landingElement = landingElement;
this.cookieName = cookieName;
this.dismissButton = dismissButton;
this.eventWrapper = {};
}
toggle() {
const isDismissed = this.isDismissed();
this.landingElement.classList.toggle('hidden', isDismissed);
if (!isDismissed) this.addEvents();
}
addEvents() {
this.eventWrapper.dismissLanding = this.dismissLanding.bind(this);
this.dismissButton.addEventListener('click', this.eventWrapper.dismissLanding);
}
removeEvents() {
this.dismissButton.removeEventListener('click', this.eventWrapper.dismissLanding);
}
dismissLanding() {
this.landingElement.classList.add('hidden');
Cookies.set(this.cookieName, 'true', { expires: 365 });
}
isDismissed() {
return Cookies.get(this.cookieName) === 'true';
}
}
export default Landing;
......@@ -57,9 +57,9 @@ require('vendor/jquery.scrollTo');
}
LineHighlighter.prototype.bindEvents = function() {
const $blobContentHolder = $('#blob-content-holder');
$blobContentHolder.on('click', 'a[data-line-number]', this.clickHandler);
$blobContentHolder.on('highlight:line', this.highlightHash);
const $fileHolder = $('.file-holder');
$fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
$fileHolder.on('highlight:line', this.highlightHash);
};
LineHighlighter.prototype.highlightHash = function() {
......
<template>
<div class="pdf-viewer" v-if="hasPDF">
<page v-for="(page, index) in pages"
:key="index"
:v-if="!loading"
:page="page"
:number="index + 1" />
</div>
</template>
<script>
import pdfjsLib from 'pdfjs-dist';
import workerSrc from 'vendor/pdf.worker';
import page from './page/index.vue';
export default {
props: {
pdf: {
type: [String, Uint8Array],
required: true,
},
},
data() {
return {
loading: false,
pages: [],
};
},
components: { page },
watch: { pdf: 'load' },
computed: {
document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
},
hasPDF() {
return this.pdf && this.pdf.length > 0;
},
},
methods: {
load() {
this.pages = [];
return pdfjsLib.getDocument(this.document)
.then(this.renderPages)
.then(() => this.$emit('pdflabload'))
.catch(error => this.$emit('pdflaberror', error))
.then(() => { this.loading = false; });
},
renderPages(pdf) {
const pagePromises = [];
this.loading = true;
for (let num = 1; num <= pdf.numPages; num += 1) {
pagePromises.push(
pdf.getPage(num).then(p => this.pages.push(p)),
);
}
return Promise.all(pagePromises);
},
},
mounted() {
pdfjsLib.PDFJS.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
};
</script>
<style>
.pdf-viewer {
background: url('./assets/img/bg.gif');
display: flex;
flex-flow: column nowrap;
}
</style>
<template>
<canvas
class="pdf-page"
ref="canvas"
:data-page="number" />
</template>
<script>
export default {
props: {
page: {
type: Object,
required: true,
},
number: {
type: Number,
required: true,
},
},
data() {
return {
scale: 4,
rendering: false,
};
},
computed: {
viewport() {
return this.page.getViewport(this.scale);
},
context() {
return this.$refs.canvas.getContext('2d');
},
renderContext() {
return {
canvasContext: this.context,
viewport: this.viewport,
};
},
},
mounted() {
this.$refs.canvas.height = this.viewport.height;
this.$refs.canvas.width = this.viewport.width;
this.rendering = true;
this.page.render(this.renderContext)
.then(() => { this.rendering = false; })
.catch(error => this.$emit('pdflaberror', error));
},
};
</script>
<style>
.pdf-page {
margin: 8px auto 0 auto;
border-top: 1px #ddd solid;
border-bottom: 1px #ddd solid;
width: 100%;
}
.pdf-page:first-child {
margin-top: 0px;
border-top: 0px;
}
.pdf-page:last-child {
margin-bottom: 0px;
border-bottom: 0px;
}
</style>
......@@ -254,6 +254,63 @@
padding: 10px 0;
}
.landing {
margin-bottom: $gl-padding;
overflow: hidden;
display: flex;
position: relative;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
background-color: $blue-25;
justify-content: center;
.dismiss-button {
position: absolute;
right: 6px;
top: 6px;
cursor: pointer;
color: $blue-300;
z-index: 1;
border: none;
background-color: transparent;
&:hover,
&:focus {
border: none;
color: $blue-400;
}
}
.svg-container {
align-self: center;
}
.inner-content {
text-align: left;
white-space: nowrap;
h4 {
color: $gl-text-color;
font-size: 17px;
}
p {
color: $gl-text-color;
margin-bottom: $gl-padding;
}
}
@media (max-width: $screen-sm-min) {
flex-direction: column;
.inner-content {
white-space: normal;
padding: 0 28px;
text-align: center;
}
}
}
.empty-state {
margin: 100px 0 0;
......
......@@ -424,6 +424,11 @@ table {
}
}
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.str-truncated {
&-60 {
@include str-truncated(60%);
......
......@@ -93,11 +93,6 @@
top: $gl-padding-top;
}
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.content-list {
li {
padding: 18px $gl-padding $gl-padding;
......@@ -139,42 +134,9 @@
}
}
.landing {
margin-bottom: $gl-padding;
overflow: hidden;
.dismiss-icon {
position: absolute;
right: $cycle-analytics-box-padding;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
}
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
.inner-content {
@media (max-width: $screen-xs-max) {
padding: 0 28px;
text-align: center;
}
h4 {
color: $gl-text-color;
font-size: 17px;
}
p {
color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding;
}
}
.landing svg {
width: 136px;
height: 136px;
}
.fa-spinner {
......
......@@ -88,3 +88,26 @@
color: $gl-text-color-secondary;
margin-top: 10px;
}
.explore-groups.landing {
margin-top: 10px;
.inner-content {
padding: 0;
p {
margin: 7px 0 0;
max-width: 480px;
padding: 0 $gl-padding;
@media (max-width: $screen-sm-min) {
margin: 0 auto;
}
}
}
svg {
width: 62px;
height: 50px;
}
}
......@@ -14,4 +14,8 @@ module RendersBlob
html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false)
}
end
def override_max_blob_size(blob)
blob.override_max_size! if params[:override_max_size] == 'true'
end
end
......@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def show
@blob.override_max_size! if params[:override_max_size] == 'true'
override_max_blob_size(@blob)
respond_to do |format|
format.html do
......
......@@ -3,6 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji
include SpammableActions
include SnippetsActions
include RendersBlob
before_action :module_enabled
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
......@@ -55,11 +56,23 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def show
@note = @project.notes.new(noteable: @snippet)
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
blob = @snippet.blob
override_max_blob_size(blob)
respond_to do |format|
format.html do
@note = @project.notes.new(noteable: @snippet)
@noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
render 'show'
end
format.json do
render_blob_json(blob)
end
end
end
def destroy
......
......@@ -3,6 +3,7 @@ class SnippetsController < ApplicationController
include SpammableActions
include SnippetsActions
include MarkdownPreview
include RendersBlob
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
......@@ -60,6 +61,18 @@ class SnippetsController < ApplicationController
end
def show
blob = @snippet.blob
override_max_blob_size(blob)
respond_to do |format|
format.html do
render 'show'
end
format.json do
render_blob_json(blob)
end
end
end
def destroy
......
......@@ -119,7 +119,15 @@ module BlobHelper
end
def blob_raw_url
namespace_project_raw_path(@project.namespace, @project, @id)
if @snippet
if @snippet.project_id
raw_namespace_project_snippet_path(@project.namespace, @project, @snippet)
else
raw_snippet_path(@snippet)
end
elsif @blob
namespace_project_raw_path(@project.namespace, @project, @id)
end
end
# SVGs can contain malicious JavaScript; only include whitelisted
......@@ -209,11 +217,21 @@ module BlobHelper
end
def copy_blob_source_button(blob)
return unless blob.rendered_as_text?(ignore_errors: false)
clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm js-copy-blob-source-btn", title: "Copy source to clipboard")
end
def open_raw_file_button(path)
link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' }
def open_raw_blob_button(blob)
if blob.raw_binary?
icon = icon('download')
title = 'Download'
else
icon = icon('file-code-o')
title = 'Open raw'
end
link_to icon, blob_raw_url, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: title, data: { container: 'body' }
end
def blob_render_error_reason(viewer)
......
......@@ -961,15 +961,13 @@ class Repository
end
def is_ancestor?(ancestor_id, descendant_id)
# NOTE: This feature is intentionally disabled until
# https://gitlab.com/gitlab-org/gitlab-ce/issues/30586 is resolved
# Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
# if is_enabled
# raw_repository.is_ancestor?(ancestor_id, descendant_id)
# else
merge_base_commit(ancestor_id, descendant_id) == ancestor_id
# end
# end
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled
raw_repository.is_ancestor?(ancestor_id, descendant_id)
else
merge_base_commit(ancestor_id, descendant_id) == ancestor_id
end
end
end
def empty_repo?
......
class Snippet < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Linguist::BlobHelper
include CacheMarkdownField
include Noteable
include Participable
......@@ -87,47 +86,26 @@ class Snippet < ActiveRecord::Base
]
end
def data
content
def blob
@blob ||= Blob.decorate(SnippetBlob.new(self), nil)
end
def hook_attrs
attributes
end
def size
0
end
def file_name
super.to_s
end
# alias for compatibility with blobs and highlighting
def path
file_name
end
def name
file_name
end
def sanitized_file_name
file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '')
end
def mode
nil
end
def visibility_level_field
:visibility_level
end
def no_highlighting?
content.lines.count > 1000
end
def notes_with_associations
notes.includes(:author)
end
......
class SnippetBlob
include Linguist::BlobHelper
attr_reader :snippet
def initialize(snippet)
@snippet = snippet
end
delegate :id, to: :snippet
def name
snippet.file_name
end
alias_method :path, :name
def size
data.bytesize
end
def data
snippet.content
end
def rendered_markup
return unless Gitlab::MarkupHelper.gitlab_markdown?(name)
Banzai.render_field(snippet, :content)
end
def mode
nil
end
def binary?
false
end
def load_all_data!(repository)
# No-op
end
def lfs_pointer?
false
end
def lfs_oid
nil
end
def lfs_size
nil
end
def truncated?
false
end
end
......@@ -2,10 +2,10 @@
%ul.nav-links
= nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups' do
Your Groups
Your groups
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore groups' do
Explore Groups
= link_to explore_groups_path, title: 'Explore public groups' do
Explore public groups
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
......
......@@ -7,6 +7,15 @@
= render 'explore/head'
= render 'nav'
- if cookies[:explore_groups_landing_dismissed] != 'true'
.explore-groups.landing.content-block.js-explore-groups-landing.hidden
%button.dismiss-button{ type: 'button', 'aria-label' => 'Dismiss' }= icon('times')
.svg-container
= custom_icon('icon_explore_groups_splash')
.inner-content
%p Below you will find all the groups that are public.
%p You can easily contribute to them by requesting to join these groups.
- if @groups.present?
= render 'groups'
- else
......
......@@ -15,8 +15,8 @@
= render 'projects/blob/viewer_switcher', blob: blob unless blame
.btn-group{ role: "group" }<
= copy_blob_source_button(blob) if !blame && blob.rendered_as_text?(ignore_errors: false)
= open_raw_file_button(namespace_project_raw_path(@project.namespace, @project, @id))
= copy_blob_source_button(blob) unless blame
= open_raw_blob_button(blob)
= view_on_environment_button(@commit.sha, @path, @environment) if @environment
.btn-group{ role: "group" }<
......
- blob = viewer.blob
- rendered_markup = blob.rendered_markup if blob.respond_to?(:rendered_markup)
.file-content.wiki
= markup(blob.name, blob.data)
= markup(blob.name, blob.data, rendered: rendered_markup)
......@@ -4,7 +4,7 @@
.project-snippets
%article.file-holder.snippet-file-content
= render 'shared/snippets/blob', raw_path: raw_namespace_project_snippet_path(@project.namespace, @project, @snippet)
= render 'shared/snippets/blob'
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
......
......@@ -39,7 +39,7 @@
.blob-content
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
= highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.no_highlighting?)
= highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.blob.no_highlighting?)
- else
.file-content.code
.nothing-here-block Empty file
<svg xmlns="http://www.w3.org/2000/svg" width="62" height="50" viewBox="260 141 62 50" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M24.6 7.7H56c3.3 0 6 2.7 6 6V44c0 3.3-2.7 6-6 6H6c-3.3 0-6-2.7-6-6V4.8C0 2 2.2 0 4.8 0h12c1.5 0 3 1 4 2l3.8 5.7z"/><mask id="e" width="62" height="50" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><path id="b" d="M4.2 13c3.7 0 4-1.7 4-4.5S7 4.8 4.2 4.8 0 5.8 0 8.5C0 11.3.5 13 4.2 13z"/><mask id="f" width="10.7" height="10.7" x="-1.2" y="-1.2"><path fill="#fff" d="M-1.2 3.6H9.5v10.7H-1.2z"/><use xlink:href="#b"/></mask><path id="c" d="M4.2 13c3.7 0 4-1.7 4-4.5S7 4.8 4.2 4.8 0 5.8 0 8.5C0 11.3.5 13 4.2 13z"/><mask id="g" width="10.7" height="10.7" x="-1.2" y="-1.2"><path fill="#fff" d="M-1.2 3.6H9.5v10.7H-1.2z"/><use xlink:href="#c"/></mask><path id="d" d="M5.4 16c4.7 0 5.3-2.3 5.3-6 0-3.5-1.7-4.6-5.3-4.6C1.7 5.4 0 6.4 0 10s.6 6 5.4 6z"/><mask id="h" width="13.1" height="13.1" x="-1.2" y="-1.2"><path fill="#fff" d="M-1.2 4.2h13v13H-1z"/><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(260 141)"><use fill="#FFF" stroke="#EEE" stroke-width="4.8" mask="url(#e)" xlink:href="#a"/><g transform="translate(33.98 22.62)"><use fill="#B5A7DD" xlink:href="#b"/><use stroke="#FFF" stroke-width="2.4" mask="url(#f)" xlink:href="#b"/><ellipse cx="4.2" cy="3" fill="#B5A7DD" stroke="#FFF" stroke-width="1.2" rx="3" ry="3"/></g><g transform="translate(19.673 22.62)"><use fill="#B5A7DD" xlink:href="#c"/><use stroke="#FFF" stroke-width="2.4" mask="url(#g)" xlink:href="#c"/><ellipse cx="4.2" cy="3" fill="#B5A7DD" stroke="#FFF" stroke-width="1.2" rx="3" ry="3"/></g><g transform="translate(25.635 21.43)"><use fill="#B5A7DD" xlink:href="#d"/><use stroke="#FFF" stroke-width="2.4" mask="url(#h)" xlink:href="#d"/><ellipse cx="5.4" cy="3.6" fill="#B5A7DD" stroke="#FFF" stroke-width="1.2" rx="3.6" ry="3.6"/></g></g></svg>
- blob = @snippet.blob
.js-file-title.file-title-flex-parent
.file-header-content
= blob_icon @snippet.mode, @snippet.path
= blob_icon blob.mode, blob.path
%strong.file-title-name
= @snippet.path
= blob.path
= copy_file_path_button(@snippet.path)
= copy_file_path_button(blob.path)
%small
= number_to_human_size(blob.raw_size)
.file-actions.hidden-xs
= render 'projects/blob/viewer_switcher', blob: blob
.btn-group{ role: "group" }<
= copy_blob_source_button(@snippet)
= open_raw_file_button(raw_path)
= copy_blob_source_button(blob)
= open_raw_blob_button(blob)
- if defined?(download_path) && download_path
= link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
- if @snippet.content.empty?
.file-content.code
.nothing-here-block Empty file
- else
- if markup?(@snippet.file_name)
.file-content.wiki
- if gitlab_markdown?(@snippet.file_name)
= preserve(markdown_field(@snippet, :content))
- else
= markup(@snippet.file_name, @snippet.content)
- else
= render 'shared/file_highlight', blob: @snippet
= render 'projects/blob/content', blob: blob
......@@ -3,7 +3,7 @@
= render 'shared/snippets/header'
%article.file-holder.snippet-file-content
= render 'shared/snippets/blob', raw_path: raw_snippet_path(@snippet), download_path: download_snippet_path(@snippet)
= render 'shared/snippets/blob', download_path: download_snippet_path(@snippet)
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
......@@ -8,7 +8,7 @@ class ExpireBuildInstanceArtifactsWorker
.reorder(nil)
.find_by(id: build_id)
return unless build.try(:project)
return unless build&.project && !build.project.pending_delete
Rails.logger.info "Removing artifacts for build #{build.id}..."
build.erase_artifacts!
......
---
title: Lazily sets UUID in ApplicationSetting for new installations
merge_request:
author:
---
title: Prevent people from creating branches if they don't have persmission to push
merge_request:
author:
---
title: Fix pipeline events description for Slack and Mattermost integration
merge_request: 10908
author:
---
title: Fix ordering of commits in the network graph
merge_request: 10936
author:
---
title: Fixed milestone sidebar showing incorrect number of MRs when collapsed
merge_request: 10933
author:
---
title: Add index on ci_runners.contacted_at
merge_request: 10876
author: blackst0ne
---
title: Show Raw button as Download for binary files
merge_request:
author:
---
title: Skip validation when creating internal (ghost, service desk) users
merge_request:
author:
---
title: Use GitLab Pages v0.4.1
title: Use blob viewers for snippets
merge_request:
author:
---
title: Gracefully handle failures for incoming emails which do not match on the To
header, and have no References header
merge_request:
author:
---
title: Ensure the chat notifications service properly saves the "Notify only default branch" setting
merge_request: 10959
author:
......@@ -6,6 +6,8 @@ if File.exist?(aws_file)
AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env]
CarrierWave.configure do |config|
config.fog_provider = 'fog/aws'
config.fog_credentials = {
provider: 'AWS', # required
aws_access_key_id: AWS_CONFIG['access_key_id'], # required
......
......@@ -82,6 +82,11 @@ var config = {
test: /\.svg$/,
loader: 'raw-loader',
},
{
test: /\.gif$/,
loader: 'url-loader',
query: { mimetype: 'image/gif' },
},
{
test: /\.(worker\.js|pdf)$/,
exclude: /node_modules/,
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddOnlyAllowMergeIfBuildSucceedsToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddRepositoryStorageToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddRequestAccessEnabledToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddRequestAccessEnabledToGroups < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class RemoveFeaturesEnabledFromProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class RemoveProjectsPushesSinceGc < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddTwoFactorColumnsToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddTwoFactorColumnsToUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddPrintingMergeRequestLinkEnabledToProject < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddAutoCancelPendingPipelinesToProject < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class RevertAddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
......
# Deleting a User Account
- As a user, you can delete your own account by navigating to **Settings** > **Account** and selecting **Delete account**
- As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Remvoe user**
- As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Remove user**
## Associated Records
......
......@@ -11,6 +11,7 @@ Feature: Project Snippets
Then I should see "Snippet one" in snippets
And I should not see "Snippet two" in snippets
@javascript
Scenario: I create new project snippet
Given I click link "New snippet"
And I submit new snippet "Snippet three"
......
......@@ -5,6 +5,7 @@ Feature: Snippets
And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet
@javascript
Scenario: I create new snippet
Given I visit new snippet page
And I submit new snippet "Personal snippet three"
......
......@@ -3,6 +3,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
include SharedProject
include SharedNote
include SharedPaths
include WaitForAjax
step 'project "Shop" have "Snippet one" snippet' do
create(:project_snippet,
......@@ -55,9 +56,10 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
fill_in "project_snippet_title", with: "Snippet three"
fill_in "project_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do
find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three'
find('.ace_editor').native.send_keys 'Content of snippet three'
end
click_button "Create snippet"
wait_for_ajax
end
step 'I should see snippet "Snippet three"' do
......@@ -79,6 +81,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
fill_in "note_note", with: "Good snippet!"
click_button "Comment"
end
wait_for_ajax
end
step 'I should see comment "Good snippet!"' do
......
......@@ -367,7 +367,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I should see buttons for allowed commands' do
page.within '.content' do
expect(page).to have_link 'Open raw'
expect(page).to have_link 'Download'
expect(page).to have_content 'History'
expect(page).to have_content 'Permalink'
expect(page).not_to have_content 'Edit'
......
......@@ -3,6 +3,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedPaths
include SharedProject
include SharedSnippet
include WaitForAjax
step 'I click link "Personal snippet one"' do
click_link "Personal snippet one"
......@@ -26,9 +27,10 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
fill_in "personal_snippet_title", with: "Personal snippet three"
fill_in "personal_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of snippet three'
find('.ace_editor').native.send_keys 'Content of snippet three'
end
click_button "Create snippet"
wait_for_ajax
end
step 'I submit new internal snippet' do
......
......@@ -70,6 +70,8 @@ module Gitlab
# Handle emails from clients which append with commas,
# example clients are Microsoft exchange and iOS app
Gitlab::IncomingEmail.scan_fallback_references(references)
when nil
[]
end
end
......
......@@ -451,7 +451,7 @@ module Gitlab
# Returns true is +from+ is direct ancestor to +to+, otherwise false
def is_ancestor?(from, to)
Gitlab::GitalyClient::Commit.is_ancestor(self, from, to)
gitaly_commit_client.is_ancestor(from, to)
end
# Return an array of Diff objects that represent the diff
......@@ -1273,6 +1273,10 @@ module Gitlab
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self)
end
def gitaly_commit_client
@gitaly_commit_client ||= Gitlab::GitalyClient::Commit.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)
......
......@@ -5,6 +5,23 @@ module Gitlab
# See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
attr_accessor :stub
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
end
def is_ancestor(ancestor_id, child_id)
request = Gitaly::CommitIsAncestorRequest.new(
repository: @gitaly_repo,
ancestor_id: ancestor_id,
child_id: child_id
)
@stub.commit_is_ancestor(request).value
end
class << self
def diff_from_parent(commit, options = {})
repository = commit.project.repository
......@@ -20,18 +37,6 @@ module Gitlab
Gitlab::Git::DiffCollection.new(stub.commit_diff(request), options)
end
def is_ancestor(repository, ancestor_id, child_id)
gitaly_repo = repository.gitaly_repository
stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: repository.gitaly_channel)
request = Gitaly::CommitIsAncestorRequest.new(
repository: gitaly_repo,
ancestor_id: ancestor_id,
child_id: child_id
)
stub.commit_is_ancestor(request).value
end
end
end
end
......
......@@ -44,9 +44,7 @@ module Gitlab
if ProtectedBranch.protected?(project, ref)
return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
has_access = project.protected_branches.protected_ref_accessible_to?(ref, user, action: :push)
has_access || !project.repository.branch_exists?(ref) && can_merge_to_branch?(ref)
project.protected_branches.protected_ref_accessible_to?(ref, user, action: :push)
else
user.can?(:push_code, project)
end
......
......@@ -34,6 +34,7 @@
"jszip-utils": "^0.0.2",
"marked": "^0.3.6",
"mousetrap": "^1.4.6",
"pdfjs-dist": "^1.8.252",
"pikaday": "^1.5.1",
"prismjs": "^1.6.0",
"raphael": "^2.2.7",
......@@ -47,6 +48,7 @@
"three-stl-loader": "^1.0.4",
"timeago.js": "^2.0.5",
"underscore": "^1.8.3",
"url-loader": "^0.5.8",
"visibilityjs": "^1.2.4",
"vue": "^2.2.6",
"vue-loader": "^11.3.4",
......
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# This cop checks for `add_column_with_default` on a table that's been
# explicitly blacklisted because of its size.
#
# Even though this helper performs the update in batches to avoid
# downtime, using it with tables with millions of rows still causes a
# significant delay in the deploy process and is best avoided.
#
# See https://gitlab.com/gitlab-com/infrastructure/issues/1602 for more
# information.
class AddColumnWithDefaultToLargeTable < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Using `add_column_with_default` on the `%s` table will take a ' \
'long time to complete, and should be avoided unless absolutely ' \
'necessary'.freeze
LARGE_TABLES = %i[
events
issues
merge_requests
namespaces
notes
projects
routes
users
].freeze
def_node_matcher :add_column_with_default?, <<~PATTERN
(send nil :add_column_with_default $(sym ...) ...)
PATTERN
def on_send(node)
return unless in_migration?(node)
matched = add_column_with_default?(node)
return unless matched
table = matched.to_a.first
return unless LARGE_TABLES.include?(table)
add_offense(node, :expression, format(MSG, table))
end
end
end
end
end
......@@ -5,29 +5,30 @@ module RuboCop
module Migration
# Cop that checks if `add_column_with_default` is used with `up`/`down` methods
# and not `change`.
class AddColumnWithDefault < RuboCop::Cop::Cop
class ReversibleAddColumnWithDefault < RuboCop::Cop::Cop
include MigrationHelpers
def_node_matcher :add_column_with_default?, <<~PATTERN
(send nil :add_column_with_default $...)
PATTERN
def_node_matcher :defines_change?, <<~PATTERN
(def :change ...)
PATTERN
MSG = '`add_column_with_default` is not reversible so you must manually define ' \
'the `up` and `down` methods in your migration class, using `remove_column` in `down`'.freeze
def on_send(node)
return unless in_migration?(node)
name = node.children[1]
return unless name == :add_column_with_default
return unless add_column_with_default?(node)
node.each_ancestor(:def) do |def_node|
next unless method_name(def_node) == :change
next unless defines_change?(def_node)
add_offense(def_node, :name)
end
end
def method_name(node)
node.children.first
end
end
end
end
......
require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default'
require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default'
......@@ -21,4 +21,3 @@ fi
echo "✔ Linting passed"
exit 0
#!/usr/bin/env ruby
require ::File.expand_path('../lib/gitlab/popen', __dir__)
tasks = [
%w[bundle exec rake config_lint],
%w[bundle exec rake flay],
%w[bundle exec rake haml_lint],
%w[bundle exec rake scss_lint],
%w[bundle exec rake brakeman],
%w[bundle exec license_finder],
%w[scripts/lint-doc.sh],
%w[yarn run eslint],
%w[bundle exec rubocop --require rubocop-rspec]
]
failed_tasks = tasks.reduce({}) do |failures, task|
output, status = Gitlab::Popen.popen(task)
puts "Running: #{task.join(' ')}"
puts output
failures[task.join(' ')] = output unless status.zero?
failures
end
if failed_tasks.empty?
puts 'All static analyses passed successfully.'
else
puts "\n===================================================\n\n"
puts "Some static analyses failed:"
failed_tasks.each do |failed_task, output|
puts "\n**** #{failed_task} failed with the following error:\n\n"
puts output
end
exit 1
end
......@@ -32,6 +32,10 @@ FactoryGirl.define do
request_access_enabled true
end
trait :with_avatar do
avatar { File.open(Rails.root.join('spec/fixtures/dk.png')) }
end
trait :repository do
# no-op... for now!
end
......
require 'spec_helper'
describe 'Admin::RequestsProfilesController', feature: true do
before do
FileUtils.mkdir_p(Gitlab::RequestProfiler::PROFILES_DIR)
login_as(:admin)
end
after do
Gitlab::RequestProfiler.remove_all_profiles
end
describe 'GET /admin/requests_profiles' do
it 'shows the current profile token' do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
visit admin_requests_profiles_path
expect(page).to have_content("X-Profile-Token: #{Gitlab::RequestProfiler.profile_token}")
end
it 'lists all available profiles' do
time1 = 1.hour.ago
time2 = 2.hours.ago
time3 = 3.hours.ago
profile1 = "|gitlab-org|gitlab-ce_#{time1.to_i}.html"
profile2 = "|gitlab-org|gitlab-ce_#{time2.to_i}.html"
profile3 = "|gitlab-com|infrastructure_#{time3.to_i}.html"
FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile1}")
FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile2}")
FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile3}")
visit admin_requests_profiles_path
within('.panel', text: '/gitlab-org/gitlab-ce') do
expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile1)}']", text: time1.to_s(:long))
expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile2)}']", text: time2.to_s(:long))
end
within('.panel', text: '/gitlab-com/infrastructure') do
expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile3)}']", text: time3.to_s(:long))
end
end
end
describe 'GET /admin/requests_profiles/:profile' do
context 'when a profile exists' do
it 'displays the content of the profile' do
content = 'This is a request profile'
profile = "|gitlab-org|gitlab-ce_#{Time.now.to_i}.html"
File.write("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile}", content)
visit admin_requests_profile_path(profile)
expect(page).to have_content(content)
end
end
context 'when a profile does not exist' do
it 'shows an error message' do
visit admin_requests_profile_path('|non|existent_12345.html')
expect(page).to have_content('Profile not found')
end
end
end
end
require 'spec_helper'
describe 'Explore Groups page', js: true, feature: true do
describe 'Explore Groups page', :js, :feature do
let!(:user) { create :user }
let!(:group) { create(:group) }
let!(:public_group) { create(:group, :public) }
......@@ -46,19 +46,39 @@ describe 'Explore Groups page', js: true, feature: true do
it 'shows non-archived projects count' do
# Initially project is not archived
expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1")
# Archive project
empty_project.archive!
visit explore_groups_path
# Check project count
expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("0")
# Unarchive project
empty_project.unarchive!
visit explore_groups_path
# Check project count
expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1")
expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("1")
end
describe 'landing component' do
it 'should show a landing component' do
expect(page).to have_content('Below you will find all the groups that are public.')
end
it 'should be dismissable' do
find('.dismiss-button').click
expect(page).not_to have_content('Below you will find all the groups that are public.')
end
it 'should persistently not show once dismissed' do
find('.dismiss-button').click
visit explore_groups_path
expect(page).not_to have_content('Below you will find all the groups that are public.')
end
end
end
......@@ -20,6 +20,7 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content('Target branch')
first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content')
find('.dropdown-source-branch .dropdown-content a', match: :first).click
expect(page).to have_content "b83d6e3"
......@@ -34,6 +35,7 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content('Target branch')
first('.js-target-branch').click
first('.dropdown-target-branch .dropdown-content')
first('.dropdown-target-branch .dropdown-content a', text: 'v1.1.0').click
expect(page).to have_content "b83d6e3"
......
require 'spec_helper'
feature 'File blob', :js, feature: true do
include TreeHelper
include WaitForAjax
let(:project) { create(:project, :public) }
def visit_blob(path, fragment = nil)
visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment)
end
context 'Ruby file' do
......@@ -27,6 +24,9 @@ feature 'File blob', :js, feature: true do
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
# shows a raw button
expect(page).to have_link('Open raw')
end
end
end
......@@ -39,7 +39,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax
end
it 'displays the blob' do
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
......@@ -53,6 +53,9 @@ feature 'File blob', :js, feature: true do
# shows a disabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
# shows a raw button
expect(page).to have_link('Open raw')
end
end
......@@ -63,7 +66,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax
end
it 'displays the blob' do
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
......@@ -84,7 +87,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax
end
it 'displays the blob' do
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
......@@ -105,7 +108,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax
end
it 'displays the blob' do
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
......@@ -163,6 +166,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a raw button
expect(page).to have_link('Open raw')
end
end
......@@ -206,6 +212,9 @@ feature 'File blob', :js, feature: true do
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
# shows a raw button
expect(page).to have_link('Open raw')
end
end
end
......@@ -240,6 +249,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a download button
expect(page).to have_link('Download')
end
end
end
......@@ -265,6 +277,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a download button
expect(page).to have_link('Download')
end
end
end
......@@ -286,6 +301,9 @@ feature 'File blob', :js, feature: true do
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
# shows a raw button
expect(page).to have_link('Open raw')
end
end
end
......@@ -308,6 +326,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a download button
expect(page).to have_link('Download')
end
end
end
......
......@@ -74,8 +74,10 @@ describe 'Cherry-pick Commits' do
wait_for_ajax
page.within('#modal-cherry-pick-commit .dropdown-menu .dropdown-content') do
click_link "'test'"
page.within('#modal-cherry-pick-commit .dropdown-menu') do
find('.dropdown-input input').set('feature')
wait_for_ajax
click_link "feature"
end
page.within('#modal-cherry-pick-commit') do
......
......@@ -200,7 +200,7 @@ feature 'Environment', :feature do
end
scenario 'user deletes the branch with running environment' do
visit namespace_project_branches_path(project.namespace, project, page: 2)
visit namespace_project_branches_path(project.namespace, project, search: 'feature')
remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click }
......
......@@ -85,8 +85,8 @@ feature 'Merge Request button', feature: true do
context 'on branches page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Merge request' }
let(:url) { namespace_project_branches_path(project.namespace, project) }
let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) }
let(:url) { namespace_project_branches_path(project.namespace, project, search: 'feature') }
let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project, search: 'feature') }
end
end
......
require 'spec_helper'
feature 'Project snippet', :js, feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) }
before do
project.team << [user, :master]
login_as(user)
end
context 'Ruby file' do
let(:file_name) { 'popen.rb' }
let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
wait_for_ajax
end
it 'displays the blob' do
aggregate_failures do
# shows highlighted Ruby code
expect(page).to have_content("require 'fileutils'")
# does not show a viewer switcher
expect(page).not_to have_selector('.js-blob-viewer-switcher')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
context 'Markdown file' do
let(:file_name) { 'ruby-style-guide.md' }
let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data }
context 'visiting directly' do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows rendered Markdown
expect(page).to have_link("PEP-8")
# shows a viewer switcher
expect(page).to have_selector('.js-blob-viewer-switcher')
# shows a disabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
end
end
context 'switching to the simple viewer' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
context 'switching to the rich viewer again' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end
context 'visiting with a line number anchor' do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet, anchor: 'L1')
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# highlights the line in question
expect(page).to have_selector('#LC1.hll')
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end
require 'rails_helper'
feature 'Create Snippet', feature: true do
feature 'Create Snippet', :js, feature: true do
before do
login_as :user
visit new_snippet_path
......@@ -9,10 +9,11 @@ feature 'Create Snippet', feature: true do
scenario 'Authenticated user creates a snippet' do
fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!'
find('.ace_editor').native.send_keys 'Hello World!'
end
click_button 'Create snippet'
wait_for_ajax
expect(page).to have_content('My Snippet Title')
expect(page).to have_content('Hello World!')
......@@ -22,10 +23,11 @@ feature 'Create Snippet', feature: true do
fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name'
find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!'
find('.ace_editor').native.send_keys 'Hello World!'
end
click_button 'Create snippet'
wait_for_ajax
expect(page).to have_content('My Snippet Title')
expect(page).to have_content('snippet+file+name')
......
require 'rails_helper'
feature 'Public Snippets', feature: true do
feature 'Public Snippets', :js, feature: true do
scenario 'Unauthenticated user should see public snippets' do
public_snippet = create(:personal_snippet, :public)
visit snippet_path(public_snippet)
wait_for_ajax
expect(page).to have_content(public_snippet.content)
end
......
require 'spec_helper'
feature 'Snippet', :js, feature: true do
let(:project) { create(:project, :repository) }
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content) }
context 'Ruby file' do
let(:file_name) { 'popen.rb' }
let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
before do
visit snippet_path(snippet)
wait_for_ajax
end
it 'displays the blob' do
aggregate_failures do
# shows highlighted Ruby code
expect(page).to have_content("require 'fileutils'")
# does not show a viewer switcher
expect(page).not_to have_selector('.js-blob-viewer-switcher')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
context 'Markdown file' do
let(:file_name) { 'ruby-style-guide.md' }
let(:content) { project.repository.blob_at('master', 'files/markdown/ruby-style-guide.md').data }
context 'visiting directly' do
before do
visit snippet_path(snippet)
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows rendered Markdown
expect(page).to have_link("PEP-8")
# shows a viewer switcher
expect(page).to have_selector('.js-blob-viewer-switcher')
# shows a disabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
end
end
context 'switching to the simple viewer' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
context 'switching to the rich viewer again' do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
wait_for_ajax
end
it 'displays the blob using the rich viewer' do
aggregate_failures do
# hides the simple viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false)
expect(page).to have_selector('.blob-viewer[data-type="rich"]')
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end
context 'visiting with a line number anchor' do
before do
visit snippet_path(snippet, anchor: 'L1')
wait_for_ajax
end
it 'displays the blob using the simple viewer' do
aggregate_failures do
# hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]')
expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false)
# highlights the line in question
expect(page).to have_selector('#LC1.hll')
# shows highlighted Markdown code
expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)")
# shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)')
end
end
end
end
end
......@@ -157,6 +157,7 @@ describe BlobHelper do
describe '#blob_render_error_options' do
before do
assign(:project, project)
assign(:blob, blob)
assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob'
......
/* eslint-disable import/no-unresolved */
import renderPDF from '~/blob/pdf';
import testPDF from './test.pdf';
import testPDF from '../../fixtures/blob/pdf/test.pdf';
describe('PDF renderer', () => {
let viewer;
......@@ -59,7 +61,7 @@ describe('PDF renderer', () => {
describe('error getting file', () => {
beforeEach((done) => {
viewer.dataset.endpoint = 'invalid/endpoint';
viewer.dataset.endpoint = 'invalid/path/to/file.pdf';
app = renderPDF();
checkLoaded(done);
......
#blob-content-holder
.file-holder
.file-content
.line-numbers
- 1.upto(25) do |i|
......
require 'spec_helper'
describe 'PDF file', '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'pdf-project') }
before(:all) do
clean_frontend_fixtures('blob/pdf/')
end
it 'blob/pdf/test.pdf' do |example|
blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf')
store_frontend_fixture(blob.data.force_encoding("utf-8"), example.description)
end
end
import Landing from '~/landing';
import Cookies from 'js-cookie';
describe('Landing', function () {
describe('class constructor', function () {
beforeEach(function () {
this.landingElement = {};
this.dismissButton = {};
this.cookieName = 'cookie_name';
this.landing = new Landing(this.landingElement, this.dismissButton, this.cookieName);
});
it('should set .landing', function () {
expect(this.landing.landingElement).toBe(this.landingElement);
});
it('should set .cookieName', function () {
expect(this.landing.cookieName).toBe(this.cookieName);
});
it('should set .dismissButton', function () {
expect(this.landing.dismissButton).toBe(this.dismissButton);
});
it('should set .eventWrapper', function () {
expect(this.landing.eventWrapper).toEqual({});
});
});
describe('toggle', function () {
beforeEach(function () {
this.isDismissed = false;
this.landingElement = { classList: jasmine.createSpyObj('classList', ['toggle']) };
this.landing = {
isDismissed: () => {},
addEvents: () => {},
landingElement: this.landingElement,
};
spyOn(this.landing, 'isDismissed').and.returnValue(this.isDismissed);
spyOn(this.landing, 'addEvents');
Landing.prototype.toggle.call(this.landing);
});
it('should call .isDismissed', function () {
expect(this.landing.isDismissed).toHaveBeenCalled();
});
it('should call .classList.toggle', function () {
expect(this.landingElement.classList.toggle).toHaveBeenCalledWith('hidden', this.isDismissed);
});
it('should call .addEvents', function () {
expect(this.landing.addEvents).toHaveBeenCalled();
});
describe('if isDismissed is true', function () {
beforeEach(function () {
this.isDismissed = true;
this.landingElement = { classList: jasmine.createSpyObj('classList', ['toggle']) };
this.landing = {
isDismissed: () => {},
addEvents: () => {},
landingElement: this.landingElement,
};
spyOn(this.landing, 'isDismissed').and.returnValue(this.isDismissed);
spyOn(this.landing, 'addEvents');
this.landing.isDismissed.calls.reset();
Landing.prototype.toggle.call(this.landing);
});
it('should not call .addEvents', function () {
expect(this.landing.addEvents).not.toHaveBeenCalled();
});
});
});
describe('addEvents', function () {
beforeEach(function () {
this.dismissButton = jasmine.createSpyObj('dismissButton', ['addEventListener']);
this.eventWrapper = {};
this.landing = {
eventWrapper: this.eventWrapper,
dismissButton: this.dismissButton,
dismissLanding: () => {},
};
Landing.prototype.addEvents.call(this.landing);
});
it('should set .eventWrapper.dismissLanding', function () {
expect(this.eventWrapper.dismissLanding).toEqual(jasmine.any(Function));
});
it('should call .addEventListener', function () {
expect(this.dismissButton.addEventListener).toHaveBeenCalledWith('click', this.eventWrapper.dismissLanding);
});
});
describe('removeEvents', function () {
beforeEach(function () {
this.dismissButton = jasmine.createSpyObj('dismissButton', ['removeEventListener']);
this.eventWrapper = { dismissLanding: () => {} };
this.landing = {
eventWrapper: this.eventWrapper,
dismissButton: this.dismissButton,
};
Landing.prototype.removeEvents.call(this.landing);
});
it('should call .removeEventListener', function () {
expect(this.dismissButton.removeEventListener).toHaveBeenCalledWith('click', this.eventWrapper.dismissLanding);
});
});
describe('dismissLanding', function () {
beforeEach(function () {
this.landingElement = { classList: jasmine.createSpyObj('classList', ['add']) };
this.cookieName = 'cookie_name';
this.landing = { landingElement: this.landingElement, cookieName: this.cookieName };
spyOn(Cookies, 'set');
Landing.prototype.dismissLanding.call(this.landing);
});
it('should call .classList.add', function () {
expect(this.landingElement.classList.add).toHaveBeenCalledWith('hidden');
});
it('should call Cookies.set', function () {
expect(Cookies.set).toHaveBeenCalledWith(this.cookieName, 'true', { expires: 365 });
});
});
describe('isDismissed', function () {
beforeEach(function () {
this.cookieName = 'cookie_name';
this.landing = { cookieName: this.cookieName };
spyOn(Cookies, 'get').and.returnValue('true');
this.isDismissed = Landing.prototype.isDismissed.call(this.landing);
});
it('should call Cookies.get', function () {
expect(Cookies.get).toHaveBeenCalledWith(this.cookieName);
});
it('should return a boolean', function () {
expect(typeof this.isDismissed).toEqual('boolean');
});
});
});
/* eslint-disable import/no-unresolved */
import Vue from 'vue';
import { PDFJS } from 'pdfjs-dist';
import workerSrc from 'vendor/pdf.worker';
import PDFLab from '~/pdf/index.vue';
import pdf from '../fixtures/blob/pdf/test.pdf';
PDFJS.workerSrc = workerSrc;
const Component = Vue.extend(PDFLab);
describe('PDF component', () => {
let vm;
const checkLoaded = (done) => {
if (vm.loading) {
setTimeout(() => {
checkLoaded(done);
}, 100);
} else {
done();
}
};
describe('without PDF data', () => {
beforeEach((done) => {
vm = new Component({
propsData: {
pdf: '',
},
});
vm.$mount();
checkLoaded(done);
});
it('does not render', () => {
expect(vm.$el.tagName).toBeUndefined();
});
});
describe('with PDF data', () => {
beforeEach((done) => {
vm = new Component({
propsData: {
pdf,
},
});
vm.$mount();
checkLoaded(done);
});
it('renders pdf component', () => {
expect(vm.$el.tagName).toBeDefined();
});
});
});
/* eslint-disable import/no-unresolved */
import Vue from 'vue';
import pdfjsLib from 'pdfjs-dist';
import workerSrc from 'vendor/pdf.worker';
import PageComponent from '~/pdf/page/index.vue';
import testPDF from '../fixtures/blob/pdf/test.pdf';
const Component = Vue.extend(PageComponent);
describe('Page component', () => {
let vm;
let testPage;
pdfjsLib.PDFJS.workerSrc = workerSrc;
const checkRendered = (done) => {
if (vm.rendering) {
setTimeout(() => {
checkRendered(done);
}, 100);
} else {
done();
}
};
beforeEach((done) => {
pdfjsLib.getDocument(testPDF)
.then(pdf => pdf.getPage(1))
.then((page) => {
testPage = page;
done();
})
.catch((error) => {
console.error(error);
});
});
describe('render', () => {
beforeEach((done) => {
vm = new Component({
propsData: {
page: testPage,
number: 1,
},
});
vm.$mount();
checkRendered(done);
});
it('renders first page', () => {
expect(vm.$el.tagName).toBeDefined();
});
});
});
......@@ -7,9 +7,17 @@ describe Gitlab::Email::Receiver, lib: true do
context "when we cannot find a capable handler" do
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") }
it "raises a UnknownIncomingEmail" do
it "raises an UnknownIncomingEmail error" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end
context "and the email contains no references header" do
let(:email_raw) { fixture_file("emails/auto_reply.eml").gsub(mail_key, "!!!") }
it "raises an UnknownIncomingEmail error" do
expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end
end
end
context "when the email is blank" do
......
require 'spec_helper'
describe Gitlab::RequestProfiler, lib: true do
describe '.profile_token' do
it 'returns a token' do
expect(described_class.profile_token).to be_present
end
it 'caches the token' do
expect(Rails.cache).to receive(:fetch).with('profile-token')
described_class.profile_token
end
end
describe '.remove_all_profiles' do
it 'removes Gitlab::RequestProfiler::PROFILES_DIR directory' do
dir = described_class::PROFILES_DIR
FileUtils.mkdir_p(dir)
expect(Dir.exist?(dir)).to be true
described_class.remove_all_profiles
expect(Dir.exist?(dir)).to be false
end
end
end
......@@ -87,10 +87,10 @@ describe Gitlab::UserAccess, lib: true do
expect(access.can_push_to_branch?(branch.name)).to be_falsey
end
it 'returns true if branch does not exist and user has permission to merge' do
it 'returns false if branch does not exist' do
project.team << [user, :developer]
expect(access.can_push_to_branch?(not_existing_branch.name)).to be_truthy
expect(access.can_push_to_branch?(not_existing_branch.name)).to be_falsey
end
end
......
......@@ -781,17 +781,14 @@ describe Project, models: true do
let(:project) { create(:empty_project) }
context 'When avatar file is uploaded' do
before do
project.update_columns(avatar: 'uploads/avatar.png')
allow(project.avatar).to receive(:present?) { true }
end
context 'when avatar file is uploaded' do
let(:project) { create(:empty_project, :with_avatar) }
let(:avatar_path) do
"/uploads/project/avatar/#{project.id}/uploads/avatar.png"
end
it 'creates a correct avatar path' do
avatar_path = "/uploads/project/avatar/#{project.id}/dk.png"
it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
expect(project.avatar_url).to eq("http://#{Gitlab.config.gitlab.host}#{avatar_path}")
end
end
context 'When avatar file in git' do
......
......@@ -1849,17 +1849,15 @@ describe Repository, models: true do
end
end
# TODO: Uncomment when feature is reenabled
# describe '#is_ancestor?' do
# context 'Gitaly is_ancestor feature enabled' do
# it 'asks Gitaly server if it\'s an ancestor' do
# commit = repository.commit
# allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(true)
# expect(Gitlab::GitalyClient::Commit).to receive(:is_ancestor).
# with(repository.raw_repository, commit.id, commit.id).and_return(true)
#
# expect(repository.is_ancestor?(commit.id, commit.id)).to be true
# end
# end
# end
describe '#is_ancestor?' do
context 'Gitaly is_ancestor feature enabled' do
it "asks Gitaly server if it's an ancestor" do
commit = repository.commit
expect(repository.raw_repository).to receive(:is_ancestor?).and_call_original
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(true)
expect(repository.is_ancestor?(commit.id, commit.id)).to be true
end
end
end
end
require 'spec_helper'
describe SnippetBlob, models: true do
let(:snippet) { create(:snippet) }
subject { described_class.new(snippet) }
describe '#id' do
it 'returns the snippet ID' do
expect(subject.id).to eq(snippet.id)
end
end
describe '#name' do
it 'returns the snippet file name' do
expect(subject.name).to eq(snippet.file_name)
end
end
describe '#size' do
it 'returns the data size' do
expect(subject.size).to eq(subject.data.bytesize)
end
end
describe '#data' do
it 'returns the snippet content' do
expect(subject.data).to eq(snippet.content)
end
end
describe '#rendered_markup' do
context 'when the content is GFM' do
let(:snippet) { create(:snippet, file_name: 'file.md') }
it 'returns the rendered GFM' do
expect(subject.rendered_markup).to eq(snippet.content_html)
end
end
context 'when the content is not GFM' do
it 'returns nil' do
expect(subject.rendered_markup).to be_nil
end
end
end
end
......@@ -5,7 +5,6 @@ describe Snippet, models: true do
subject { described_class }
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
it { is_expected.to include_module(Linguist::BlobHelper) }
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
......@@ -241,4 +240,16 @@ describe Snippet, models: true do
end
end
end
describe '#blob' do
let(:snippet) { create(:snippet) }
it 'returns a blob representing the snippet data' do
blob = snippet.blob
expect(blob).to be_a(Blob)
expect(blob.path).to eq(snippet.file_name)
expect(blob.data).to eq(snippet.content)
end
end
end
require 'spec_helper'
describe 'Request Profiler' do
let(:user) { create(:user) }
shared_examples 'profiling a request' do
before do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
allow(RubyProf::Profile).to receive(:profile) do |&blk|
blk.call
RubyProf::Profile.new
end
end
it 'creates a profile of the request' do
project = create(:project, namespace: user.namespace)
time = Time.now
path = "/#{project.path_with_namespace}"
Timecop.freeze(time) do
get path, nil, 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token
end
profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}.html"
expect(File.exist?(profile_path)).to be true
end
after do
Gitlab::RequestProfiler.remove_all_profiles
end
end
context "when user is logged-in" do
before do
login_as(user)
end
include_examples 'profiling a request'
end
context "when user is not logged-in" do
include_examples 'profiling a request'
end
end
require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/add_column_with_default_to_large_table'
describe RuboCop::Cop::Migration::AddColumnWithDefaultToLargeTable do
include CopHelper
subject(:cop) { described_class.new }
context 'in migration' do
before do
allow(cop).to receive(:in_migration?).and_return(true)
end
described_class::LARGE_TABLES.each do |table|
it "registers an offense for the #{table} table" do
inspect_source(cop, "add_column_with_default :#{table}, :column, default: true")
aggregate_failures do
expect(cop.offenses.size).to eq(1)
expect(cop.offenses.map(&:line)).to eq([1])
end
end
end
it 'registers no offense for non-blacklisted tables' do
inspect_source(cop, "add_column_with_default :table, :column, default: true")
expect(cop.offenses).to be_empty
end
end
context 'outside of migration' do
it 'registers no offense' do
table = described_class::LARGE_TABLES.sample
inspect_source(cop, "add_column_with_default :#{table}, :column, default: true")
expect(cop.offenses).to be_empty
end
end
end
......@@ -3,9 +3,9 @@ require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/add_column_with_default'
require_relative '../../../../rubocop/cop/migration/reversible_add_column_with_default'
describe RuboCop::Cop::Migration::AddColumnWithDefault do
describe RuboCop::Cop::Migration::ReversibleAddColumnWithDefault do
include CopHelper
subject(:cop) { described_class.new }
......
......@@ -40,7 +40,8 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
'add-ipython-files' => '6d85bb69'
'add-ipython-files' => '6d85bb69',
'add-pdf-file' => 'e774ebd3'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
......
......@@ -21,6 +21,7 @@ describe 'projects/blob/_viewer.html.haml', :view do
before do
assign(:project, project)
assign(:blob, blob)
assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob'
......
......@@ -34,12 +34,14 @@ describe ExpireBuildInstanceArtifactsWorker do
context 'when associated project was removed' do
let(:build) do
create(:ci_build, :artifacts, artifacts_expiry) do |build|
build.project.delete
build.project.pending_delete = true
end
end
it 'does not remove artifacts' do
expect(build.reload.artifacts_file.exists?).to be_truthy
expect do
build.reload.artifacts_file
end.not_to raise_error
end
end
end
......
......@@ -3638,7 +3638,7 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7:
dependencies:
mime-db "~1.26.0"
mime@1.3.4, mime@^1.3.4:
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"
......@@ -3710,6 +3710,10 @@ nested-error-stacks@^1.0.0:
dependencies:
inherits "~2.0.1"
node-ensure@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
node-libs-browser@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-1.1.1.tgz#2a38243abedd7dffcd07a97c9aca5668975a6fea"
......@@ -4102,6 +4106,13 @@ pbkdf2@^3.0.3:
dependencies:
create-hmac "^1.1.2"
pdfjs-dist@^1.8.252:
version "1.8.252"
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-1.8.252.tgz#2477245695341f7fe096824dacf327bc324c0f52"
dependencies:
node-ensure "^0.0.0"
worker-loader "^0.8.0"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
......@@ -5542,6 +5553,13 @@ update-notifier@0.5.0:
semver-diff "^2.0.0"
string-length "^1.0.0"
url-loader@^0.5.8:
version "0.5.8"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.8.tgz#b9183b1801e0f847718673673040bc9dc1c715c5"
dependencies:
loader-utils "^1.0.2"
mime "1.3.x"
url-parse@1.0.x:
version "1.0.5"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
......@@ -5825,6 +5843,12 @@ wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
worker-loader@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-0.8.0.tgz#13582960dcd7d700dc829d3fd252a7561696167e"
dependencies:
loader-utils "^1.0.2"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment