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 ...@@ -45,6 +45,7 @@ eslint-report.html
/public/uploads.* /public/uploads.*
/public/uploads/ /public/uploads/
/shared/artifacts/ /shared/artifacts/
/spec/javascripts/fixtures/blob/pdf/
/rails_best_practices_output.html /rails_best_practices_output.html
/tags /tags
/tmp/* /tmp/*
......
...@@ -253,38 +253,32 @@ spinach mysql 9 10: *spinach-knapsack-mysql ...@@ -253,38 +253,32 @@ spinach mysql 9 10: *spinach-knapsack-mysql
SETUP_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
.exec: &exec .rake-exec: &rake-exec
<<: *ruby-static-analysis <<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
stage: test stage: test
script: script:
- bundle exec $CI_JOB_NAME - bundle exec rake $CI_JOB_NAME
rubocop: static-analysis:
<<: *ruby-static-analysis <<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
stage: test stage: test
script: script:
- bundle exec "rubocop --require rubocop-rspec" - scripts/static-analysis
rake haml_lint: *exec downtime_check:
rake scss_lint: *exec <<: *rake-exec
rake config_lint: *exec
rake brakeman: *exec
rake flay: *exec
license_finder: *exec
rake downtime_check:
<<: *exec
except: except:
- master - master
- tags - tags
- /^[\d-]+-stable(-ee)?$/ - /^[\d-]+-stable(-ee)?$/
- /^docs\/*/ - /^docs\/*/
rake ee_compat_check: ee_compat_check:
<<: *exec <<: *rake-exec
only: only:
- branches@gitlab-org/gitlab-ce - branches@gitlab-org/gitlab-ce
except: except:
...@@ -309,12 +303,12 @@ rake ee_compat_check: ...@@ -309,12 +303,12 @@ rake ee_compat_check:
script: script:
- bundle exec rake db:migrate:reset - bundle exec rake db:migrate:reset
rake pg db:migrate:reset: db:migrate:reset pg:
<<: *db-migrate-reset <<: *db-migrate-reset
<<: *use-pg <<: *use-pg
<<: *except-docs <<: *except-docs
rake mysql db:migrate:reset: db:migrate:reset mysql:
<<: *db-migrate-reset <<: *db-migrate-reset
<<: *use-mysql <<: *use-mysql
<<: *except-docs <<: *except-docs
...@@ -326,12 +320,12 @@ rake mysql db:migrate:reset: ...@@ -326,12 +320,12 @@ rake mysql db:migrate:reset:
- bundle exec rake db:rollback STEP=120 - bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate - bundle exec rake db:migrate
rake pg db:rollback: db:rollback pg:
<<: *db-rollback <<: *db-rollback
<<: *use-pg <<: *use-pg
<<: *except-docs <<: *except-docs
rake mysql db:rollback: db:rollback mysql:
<<: *db-rollback <<: *db-rollback
<<: *use-mysql <<: *use-mysql
<<: *except-docs <<: *except-docs
...@@ -353,17 +347,17 @@ rake mysql db:rollback: ...@@ -353,17 +347,17 @@ rake mysql db:rollback:
paths: paths:
- log/development.log - log/development.log
rake pg db:seed_fu: db:seed_fu pg:
<<: *db-seed_fu <<: *db-seed_fu
<<: *use-pg <<: *use-pg
<<: *except-docs <<: *except-docs
rake mysql db:seed_fu: db:seed_fu mysql:
<<: *db-seed_fu <<: *db-seed_fu
<<: *use-mysql <<: *use-mysql
<<: *except-docs <<: *except-docs
rake gitlab:assets:compile: gitlab:assets:compile:
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
...@@ -383,7 +377,7 @@ rake gitlab:assets:compile: ...@@ -383,7 +377,7 @@ rake gitlab:assets:compile:
paths: paths:
- webpack-report/ - webpack-report/
rake karma: karma:
cache: cache:
paths: paths:
- vendor/ruby - vendor/ruby
...@@ -402,16 +396,6 @@ rake karma: ...@@ -402,16 +396,6 @@ rake karma:
paths: paths:
- coverage-javascript/ - coverage-javascript/
docs:check:apilint:
image: "phusion/baseimage"
stage: test
<<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
script:
- scripts/lint-doc.sh
docs:check:links: docs:check:links:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine" image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
stage: test stage: test
...@@ -459,11 +443,11 @@ bundler:audit: ...@@ -459,11 +443,11 @@ bundler:audit:
- . scripts/prepare_build.sh - . scripts/prepare_build.sh
- bundle exec rake db:migrate - bundle exec rake db:migrate
migration pg paths: migration path pg:
<<: *migration-paths <<: *migration-paths
<<: *use-pg <<: *use-pg
migration mysql paths: migration path mysql:
<<: *migration-paths <<: *migration-paths
<<: *use-mysql <<: *use-mysql
...@@ -485,14 +469,6 @@ coverage: ...@@ -485,14 +469,6 @@ coverage:
- coverage/index.html - coverage/index.html
- coverage/assets/ - coverage/assets/
lint:javascript:
<<: *dedicated-runner
<<: *except-docs
stage: test
before_script: []
script:
- yarn run eslint
lint:javascript:report: lint:javascript:report:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
...@@ -548,8 +524,8 @@ pages: ...@@ -548,8 +524,8 @@ pages:
<<: *dedicated-runner <<: *dedicated-runner
dependencies: dependencies:
- coverage - coverage
- rake karma - karma
- rake gitlab:assets:compile - gitlab:assets:compile
- lint:javascript:report - lint:javascript:report
script: script:
- mv public/ .public/ - mv public/ .public/
......
...@@ -2,6 +2,17 @@ ...@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. 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) ## 9.1.1 (2017-04-26)
- Add a transaction around move_issues_to_ghost_user. !10465 - Add a transaction around move_issues_to_ghost_user. !10465
......
...@@ -85,14 +85,14 @@ gem 'kaminari', '~> 0.17.0' ...@@ -85,14 +85,14 @@ gem 'kaminari', '~> 0.17.0'
gem 'hamlit', '~> 2.6.1' gem 'hamlit', '~> 2.6.1'
# Files attachments # Files attachments
gem 'carrierwave', '~> 0.11.0' gem 'carrierwave', '~> 1.0'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
# for backups # for backups
gem 'fog-aws', '~> 0.9' gem 'fog-aws', '~> 0.9'
gem 'fog-core', '~> 1.40' gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5' gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1' gem 'fog-openstack', '~> 0.1'
......
...@@ -105,12 +105,10 @@ GEM ...@@ -105,12 +105,10 @@ GEM
capybara-screenshot (1.0.14) capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3) capybara (>= 1.0, < 3)
launchy launchy
carrierwave (0.11.2) carrierwave (1.0.0)
activemodel (>= 3.2.0) activemodel (>= 4.0.0)
activesupport (>= 3.2.0) activesupport (>= 4.0.0)
json (>= 1.7)
mime-types (>= 1.16) mime-types (>= 1.16)
mimemagic (>= 0.3.0)
cause (0.1) cause (0.1)
charlock_holmes (0.7.3) charlock_holmes (0.7.3)
chronic (0.10.2) chronic (0.10.2)
...@@ -184,7 +182,7 @@ GEM ...@@ -184,7 +182,7 @@ GEM
erubis (2.7.0) erubis (2.7.0)
escape_utils (1.1.1) escape_utils (1.1.1)
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.52.0) excon (0.55.0)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
extlib (0.9.16) extlib (0.9.16)
...@@ -210,12 +208,12 @@ GEM ...@@ -210,12 +208,12 @@ GEM
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
fog-aws (0.11.0) fog-aws (0.13.0)
fog-core (~> 1.38) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
fog-core (1.42.0) fog-core (1.44.1)
builder builder
excon (~> 0.49) excon (~> 0.49)
formatador (~> 0.2) formatador (~> 0.2)
...@@ -237,9 +235,9 @@ GEM ...@@ -237,9 +235,9 @@ GEM
fog-json (>= 1.0) fog-json (>= 1.0)
fog-xml (>= 0.1) fog-xml (>= 0.1)
ipaddress (>= 0.8) ipaddress (>= 0.8)
fog-xml (0.1.2) fog-xml (0.1.3)
fog-core fog-core
nokogiri (~> 1.5, >= 1.5.11) nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1) font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1) railties (>= 3.2, < 5.1)
foreman (0.78.0) foreman (0.78.0)
...@@ -871,7 +869,7 @@ DEPENDENCIES ...@@ -871,7 +869,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
capybara (~> 2.6.2) capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.11.0) carrierwave (~> 1.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2) chronic (~> 0.10.2)
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
...@@ -896,7 +894,7 @@ DEPENDENCIES ...@@ -896,7 +894,7 @@ DEPENDENCIES
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.8.0) flay (~> 2.8.0)
fog-aws (~> 0.9) fog-aws (~> 0.9)
fog-core (~> 1.40) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 0.5)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
......
/* eslint-disable no-new */ /* eslint-disable no-new */
import Vue from 'vue'; import Vue from 'vue';
import PDFLab from 'vendor/pdflab'; import pdfLab from '../../pdf/index.vue';
import workerSrc from 'vendor/pdf.worker';
Vue.use(PDFLab, {
workerSrc,
});
export default () => { export default () => {
const el = document.getElementById('js-pdf-viewer'); const el = document.getElementById('js-pdf-viewer');
...@@ -20,6 +15,9 @@ export default () => { ...@@ -20,6 +15,9 @@ export default () => {
pdf: el.dataset.endpoint, pdf: el.dataset.endpoint,
}; };
}, },
components: {
pdfLab,
},
methods: { methods: {
onLoad() { onLoad() {
this.loading = false; this.loading = false;
......
...@@ -6,7 +6,7 @@ export default class BlobViewer { ...@@ -6,7 +6,7 @@ export default class BlobViewer {
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn'); this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]'); this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]'); 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'); let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type');
...@@ -82,7 +82,7 @@ export default class BlobViewer { ...@@ -82,7 +82,7 @@ export default class BlobViewer {
viewer.setAttribute('data-loaded', 'true'); viewer.setAttribute('data-loaded', 'true');
this.$blobContentHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}); });
......
...@@ -44,6 +44,7 @@ import GroupsList from './groups_list'; ...@@ -44,6 +44,7 @@ import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion'; import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout'; import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags'; import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
...@@ -148,8 +149,19 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -148,8 +149,19 @@ const ShortcutsBlob = require('./shortcuts_blob');
new ProjectsList(); new ProjectsList();
break; break;
case 'dashboard:groups:index': case 'dashboard:groups:index':
new GroupsList();
break;
case 'explore:groups:index': case 'explore:groups:index':
new GroupsList(); 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; break;
case 'projects:milestones:new': case 'projects:milestones:new':
case 'projects:milestones:edit': case 'projects:milestones:edit':
...@@ -356,6 +368,10 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -356,6 +368,10 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'users:show': case 'users:show':
new UserCallout(); new UserCallout();
break; break;
case 'snippets:show':
new LineHighlighter();
new BlobViewer();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'sessions': case 'sessions':
...@@ -434,6 +450,8 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -434,6 +450,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') { if (path[2] === 'show') {
new ZenMode(); new ZenMode();
new LineHighlighter();
new BlobViewer();
} }
break; break;
case 'labels': 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'); ...@@ -57,9 +57,9 @@ require('vendor/jquery.scrollTo');
} }
LineHighlighter.prototype.bindEvents = function() { LineHighlighter.prototype.bindEvents = function() {
const $blobContentHolder = $('#blob-content-holder'); const $fileHolder = $('.file-holder');
$blobContentHolder.on('click', 'a[data-line-number]', this.clickHandler); $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
$blobContentHolder.on('highlight:line', this.highlightHash); $fileHolder.on('highlight:line', this.highlightHash);
}; };
LineHighlighter.prototype.highlightHash = function() { 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 @@ ...@@ -254,6 +254,63 @@
padding: 10px 0; 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 { .empty-state {
margin: 100px 0 0; margin: 100px 0 0;
......
...@@ -424,6 +424,11 @@ table { ...@@ -424,6 +424,11 @@ table {
} }
} }
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.str-truncated { .str-truncated {
&-60 { &-60 {
@include str-truncated(60%); @include str-truncated(60%);
......
...@@ -93,11 +93,6 @@ ...@@ -93,11 +93,6 @@
top: $gl-padding-top; top: $gl-padding-top;
} }
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.content-list { .content-list {
li { li {
padding: 18px $gl-padding $gl-padding; padding: 18px $gl-padding $gl-padding;
...@@ -139,42 +134,9 @@ ...@@ -139,42 +134,9 @@
} }
} }
.landing { .landing svg {
margin-bottom: $gl-padding; width: 136px;
overflow: hidden; height: 136px;
.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;
}
}
} }
.fa-spinner { .fa-spinner {
......
...@@ -88,3 +88,26 @@ ...@@ -88,3 +88,26 @@
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
margin-top: 10px; 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 ...@@ -14,4 +14,8 @@ module RendersBlob
html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false) html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false)
} }
end end
def override_max_blob_size(blob)
blob.override_max_size! if params[:override_max_size] == 'true'
end
end end
...@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController
end end
def show def show
@blob.override_max_size! if params[:override_max_size] == 'true' override_max_blob_size(@blob)
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -3,6 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji include ToggleAwardEmoji
include SpammableActions include SpammableActions
include SnippetsActions include SnippetsActions
include RendersBlob
before_action :module_enabled before_action :module_enabled
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
...@@ -55,11 +56,23 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -55,11 +56,23 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def show def show
@note = @project.notes.new(noteable: @snippet) blob = @snippet.blob
@noteable = @snippet override_max_blob_size(blob)
@discussions = @snippet.discussions respond_to do |format|
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) 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 end
def destroy def destroy
......
...@@ -3,6 +3,7 @@ class SnippetsController < ApplicationController ...@@ -3,6 +3,7 @@ class SnippetsController < ApplicationController
include SpammableActions include SpammableActions
include SnippetsActions include SnippetsActions
include MarkdownPreview include MarkdownPreview
include RendersBlob
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
...@@ -60,6 +61,18 @@ class SnippetsController < ApplicationController ...@@ -60,6 +61,18 @@ class SnippetsController < ApplicationController
end end
def show 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 end
def destroy def destroy
......
...@@ -119,7 +119,15 @@ module BlobHelper ...@@ -119,7 +119,15 @@ module BlobHelper
end end
def blob_raw_url 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 end
# SVGs can contain malicious JavaScript; only include whitelisted # SVGs can contain malicious JavaScript; only include whitelisted
...@@ -209,11 +217,21 @@ module BlobHelper ...@@ -209,11 +217,21 @@ module BlobHelper
end end
def copy_blob_source_button(blob) 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") 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 end
def open_raw_file_button(path) def open_raw_blob_button(blob)
link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' } 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 end
def blob_render_error_reason(viewer) def blob_render_error_reason(viewer)
......
...@@ -961,15 +961,13 @@ class Repository ...@@ -961,15 +961,13 @@ class Repository
end end
def is_ancestor?(ancestor_id, descendant_id) def is_ancestor?(ancestor_id, descendant_id)
# NOTE: This feature is intentionally disabled until Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
# https://gitlab.com/gitlab-org/gitlab-ce/issues/30586 is resolved if is_enabled
# Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled| raw_repository.is_ancestor?(ancestor_id, descendant_id)
# if is_enabled else
# raw_repository.is_ancestor?(ancestor_id, descendant_id) merge_base_commit(ancestor_id, descendant_id) == ancestor_id
# else end
merge_base_commit(ancestor_id, descendant_id) == ancestor_id end
# end
# end
end end
def empty_repo? def empty_repo?
......
class Snippet < ActiveRecord::Base class Snippet < ActiveRecord::Base
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Linguist::BlobHelper
include CacheMarkdownField include CacheMarkdownField
include Noteable include Noteable
include Participable include Participable
...@@ -87,47 +86,26 @@ class Snippet < ActiveRecord::Base ...@@ -87,47 +86,26 @@ class Snippet < ActiveRecord::Base
] ]
end end
def data def blob
content @blob ||= Blob.decorate(SnippetBlob.new(self), nil)
end end
def hook_attrs def hook_attrs
attributes attributes
end end
def size
0
end
def file_name def file_name
super.to_s super.to_s
end end
# alias for compatibility with blobs and highlighting
def path
file_name
end
def name
file_name
end
def sanitized_file_name def sanitized_file_name
file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '') file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '')
end end
def mode
nil
end
def visibility_level_field def visibility_level_field
:visibility_level :visibility_level
end end
def no_highlighting?
content.lines.count > 1000
end
def notes_with_associations def notes_with_associations
notes.includes(:author) notes.includes(:author)
end 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 @@ ...@@ -2,10 +2,10 @@
%ul.nav-links %ul.nav-links
= nav_link(page: dashboard_groups_path) do = nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups' do = link_to dashboard_groups_path, title: 'Your groups' do
Your Groups Your groups
= nav_link(page: explore_groups_path) do = nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore groups' do = link_to explore_groups_path, title: 'Explore public groups' do
Explore Groups Explore public groups
.nav-controls .nav-controls
= render 'shared/groups/search_form' = render 'shared/groups/search_form'
= render 'shared/groups/dropdown' = render 'shared/groups/dropdown'
......
...@@ -7,6 +7,15 @@ ...@@ -7,6 +7,15 @@
= render 'explore/head' = render 'explore/head'
= render 'nav' = 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? - if @groups.present?
= render 'groups' = render 'groups'
- else - else
......
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
= render 'projects/blob/viewer_switcher', blob: blob unless blame = render 'projects/blob/viewer_switcher', blob: blob unless blame
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
= copy_blob_source_button(blob) if !blame && blob.rendered_as_text?(ignore_errors: false) = copy_blob_source_button(blob) unless blame
= open_raw_file_button(namespace_project_raw_path(@project.namespace, @project, @id)) = open_raw_blob_button(blob)
= view_on_environment_button(@commit.sha, @path, @environment) if @environment = view_on_environment_button(@commit.sha, @path, @environment) if @environment
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
......
- blob = viewer.blob - blob = viewer.blob
- rendered_markup = blob.rendered_markup if blob.respond_to?(:rendered_markup)
.file-content.wiki .file-content.wiki
= markup(blob.name, blob.data) = markup(blob.name, blob.data, rendered: rendered_markup)
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.project-snippets .project-snippets
%article.file-holder.snippet-file-content %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 .row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true = render 'award_emoji/awards_block', awardable: @snippet, inline: true
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
.blob-content .blob-content
- snippet_chunks.each do |chunk| - snippet_chunks.each do |chunk|
- unless chunk[:data].empty? - 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 - else
.file-content.code .file-content.code
.nothing-here-block Empty file .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 .js-file-title.file-title-flex-parent
.file-header-content .file-header-content
= blob_icon @snippet.mode, @snippet.path = blob_icon blob.mode, blob.path
%strong.file-title-name %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 .file-actions.hidden-xs
= render 'projects/blob/viewer_switcher', blob: blob
.btn-group{ role: "group" }< .btn-group{ role: "group" }<
= copy_blob_source_button(@snippet) = copy_blob_source_button(blob)
= open_raw_file_button(raw_path) = open_raw_blob_button(blob)
- if defined?(download_path) && download_path - if defined?(download_path) && download_path
= link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' } = link_to icon('download'), download_path, class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
- if @snippet.content.empty? = render 'projects/blob/content', blob: blob
.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
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= render 'shared/snippets/header' = render 'shared/snippets/header'
%article.file-holder.snippet-file-content %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 .row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true = render 'award_emoji/awards_block', awardable: @snippet, inline: true
...@@ -8,7 +8,7 @@ class ExpireBuildInstanceArtifactsWorker ...@@ -8,7 +8,7 @@ class ExpireBuildInstanceArtifactsWorker
.reorder(nil) .reorder(nil)
.find_by(id: build_id) .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}..." Rails.logger.info "Removing artifacts for build #{build.id}..."
build.erase_artifacts! 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: merge_request:
author: 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) ...@@ -6,6 +6,8 @@ if File.exist?(aws_file)
AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env] AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env]
CarrierWave.configure do |config| CarrierWave.configure do |config|
config.fog_provider = 'fog/aws'
config.fog_credentials = { config.fog_credentials = {
provider: 'AWS', # required provider: 'AWS', # required
aws_access_key_id: AWS_CONFIG['access_key_id'], # required aws_access_key_id: AWS_CONFIG['access_key_id'], # required
......
...@@ -82,6 +82,11 @@ var config = { ...@@ -82,6 +82,11 @@ var config = {
test: /\.svg$/, test: /\.svg$/,
loader: 'raw-loader', loader: 'raw-loader',
}, },
{
test: /\.gif$/,
loader: 'url-loader',
query: { mimetype: 'image/gif' },
},
{ {
test: /\.(worker\.js|pdf)$/, test: /\.(worker\.js|pdf)$/,
exclude: /node_modules/, exclude: /node_modules/,
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddOnlyAllowMergeIfBuildSucceedsToProjects < ActiveRecord::Migration class AddOnlyAllowMergeIfBuildSucceedsToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddRepositoryStorageToProjects < ActiveRecord::Migration class AddRepositoryStorageToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddRequestAccessEnabledToProjects < ActiveRecord::Migration class AddRequestAccessEnabledToProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddRequestAccessEnabledToGroups < ActiveRecord::Migration class AddRequestAccessEnabledToGroups < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class RemoveFeaturesEnabledFromProjects < ActiveRecord::Migration class RemoveFeaturesEnabledFromProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class RemoveProjectsPushesSinceGc < ActiveRecord::Migration class RemoveProjectsPushesSinceGc < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddTwoFactorColumnsToNamespaces < ActiveRecord::Migration class AddTwoFactorColumnsToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddTwoFactorColumnsToUsers < ActiveRecord::Migration class AddTwoFactorColumnsToUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddPrintingMergeRequestLinkEnabledToProject < ActiveRecord::Migration class AddPrintingMergeRequestLinkEnabledToProject < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class AddAutoCancelPendingPipelinesToProject < ActiveRecord::Migration class AddAutoCancelPendingPipelinesToProject < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/AddColumnWithDefaultToLargeTable
class RevertAddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration class RevertAddNotifiedOfOwnActivityToUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
disable_ddl_transaction! disable_ddl_transaction!
......
# Deleting a User Account # Deleting a User Account
- As a user, you can delete your own account by navigating to **Settings** > **Account** and selecting **Delete 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 ## Associated Records
......
...@@ -11,6 +11,7 @@ Feature: Project Snippets ...@@ -11,6 +11,7 @@ Feature: Project Snippets
Then I should see "Snippet one" in snippets Then I should see "Snippet one" in snippets
And I should not see "Snippet two" in snippets And I should not see "Snippet two" in snippets
@javascript
Scenario: I create new project snippet Scenario: I create new project snippet
Given I click link "New snippet" Given I click link "New snippet"
And I submit new snippet "Snippet three" And I submit new snippet "Snippet three"
......
...@@ -5,6 +5,7 @@ Feature: Snippets ...@@ -5,6 +5,7 @@ Feature: Snippets
And I have public "Personal snippet one" snippet And I have public "Personal snippet one" snippet
And I have private "Personal snippet private" snippet And I have private "Personal snippet private" snippet
@javascript
Scenario: I create new snippet Scenario: I create new snippet
Given I visit new snippet page Given I visit new snippet page
And I submit new snippet "Personal snippet three" And I submit new snippet "Personal snippet three"
......
...@@ -3,6 +3,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps ...@@ -3,6 +3,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
include SharedProject include SharedProject
include SharedNote include SharedNote
include SharedPaths include SharedPaths
include WaitForAjax
step 'project "Shop" have "Snippet one" snippet' do step 'project "Shop" have "Snippet one" snippet' do
create(:project_snippet, create(:project_snippet,
...@@ -55,9 +56,10 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps ...@@ -55,9 +56,10 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
fill_in "project_snippet_title", with: "Snippet three" fill_in "project_snippet_title", with: "Snippet three"
fill_in "project_snippet_file_name", with: "my_snippet.rb" fill_in "project_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do 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 end
click_button "Create snippet" click_button "Create snippet"
wait_for_ajax
end end
step 'I should see snippet "Snippet three"' do step 'I should see snippet "Snippet three"' do
...@@ -79,6 +81,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps ...@@ -79,6 +81,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
fill_in "note_note", with: "Good snippet!" fill_in "note_note", with: "Good snippet!"
click_button "Comment" click_button "Comment"
end end
wait_for_ajax
end end
step 'I should see comment "Good snippet!"' do step 'I should see comment "Good snippet!"' do
......
...@@ -367,7 +367,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -367,7 +367,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I should see buttons for allowed commands' do step 'I should see buttons for allowed commands' do
page.within '.content' 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 'History'
expect(page).to have_content 'Permalink' expect(page).to have_content 'Permalink'
expect(page).not_to have_content 'Edit' expect(page).not_to have_content 'Edit'
......
...@@ -3,6 +3,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps ...@@ -3,6 +3,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedPaths include SharedPaths
include SharedProject include SharedProject
include SharedSnippet include SharedSnippet
include WaitForAjax
step 'I click link "Personal snippet one"' do step 'I click link "Personal snippet one"' do
click_link "Personal snippet one" click_link "Personal snippet one"
...@@ -26,9 +27,10 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps ...@@ -26,9 +27,10 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
fill_in "personal_snippet_title", with: "Personal snippet three" fill_in "personal_snippet_title", with: "Personal snippet three"
fill_in "personal_snippet_file_name", with: "my_snippet.rb" fill_in "personal_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do 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 end
click_button "Create snippet" click_button "Create snippet"
wait_for_ajax
end end
step 'I submit new internal snippet' do step 'I submit new internal snippet' do
......
...@@ -70,6 +70,8 @@ module Gitlab ...@@ -70,6 +70,8 @@ module Gitlab
# Handle emails from clients which append with commas, # Handle emails from clients which append with commas,
# example clients are Microsoft exchange and iOS app # example clients are Microsoft exchange and iOS app
Gitlab::IncomingEmail.scan_fallback_references(references) Gitlab::IncomingEmail.scan_fallback_references(references)
when nil
[]
end end
end end
......
...@@ -451,7 +451,7 @@ module Gitlab ...@@ -451,7 +451,7 @@ module Gitlab
# Returns true is +from+ is direct ancestor to +to+, otherwise false # Returns true is +from+ is direct ancestor to +to+, otherwise false
def is_ancestor?(from, to) def is_ancestor?(from, to)
Gitlab::GitalyClient::Commit.is_ancestor(self, from, to) gitaly_commit_client.is_ancestor(from, to)
end end
# Return an array of Diff objects that represent the diff # Return an array of Diff objects that represent the diff
...@@ -1273,6 +1273,10 @@ module Gitlab ...@@ -1273,6 +1273,10 @@ module Gitlab
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self) @gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self)
end end
def gitaly_commit_client
@gitaly_commit_client ||= Gitlab::GitalyClient::Commit.new(self)
end
# Returns the `Rugged` sorting type constant for a given # Returns the `Rugged` sorting type constant for a given
# sort type key. Valid keys are `:none`, `:topo`, and `:date` # sort type key. Valid keys are `:none`, `:topo`, and `:date`
def rugged_sort_type(key) def rugged_sort_type(key)
......
...@@ -5,6 +5,23 @@ module Gitlab ...@@ -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 # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze 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 class << self
def diff_from_parent(commit, options = {}) def diff_from_parent(commit, options = {})
repository = commit.project.repository repository = commit.project.repository
...@@ -20,18 +37,6 @@ module Gitlab ...@@ -20,18 +37,6 @@ module Gitlab
Gitlab::Git::DiffCollection.new(stub.commit_diff(request), options) Gitlab::Git::DiffCollection.new(stub.commit_diff(request), options)
end 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 end
end end
......
...@@ -44,9 +44,7 @@ module Gitlab ...@@ -44,9 +44,7 @@ module Gitlab
if ProtectedBranch.protected?(project, ref) if ProtectedBranch.protected?(project, ref)
return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user) 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) project.protected_branches.protected_ref_accessible_to?(ref, user, action: :push)
has_access || !project.repository.branch_exists?(ref) && can_merge_to_branch?(ref)
else else
user.can?(:push_code, project) user.can?(:push_code, project)
end end
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
"jszip-utils": "^0.0.2", "jszip-utils": "^0.0.2",
"marked": "^0.3.6", "marked": "^0.3.6",
"mousetrap": "^1.4.6", "mousetrap": "^1.4.6",
"pdfjs-dist": "^1.8.252",
"pikaday": "^1.5.1", "pikaday": "^1.5.1",
"prismjs": "^1.6.0", "prismjs": "^1.6.0",
"raphael": "^2.2.7", "raphael": "^2.2.7",
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
"three-stl-loader": "^1.0.4", "three-stl-loader": "^1.0.4",
"timeago.js": "^2.0.5", "timeago.js": "^2.0.5",
"underscore": "^1.8.3", "underscore": "^1.8.3",
"url-loader": "^0.5.8",
"visibilityjs": "^1.2.4", "visibilityjs": "^1.2.4",
"vue": "^2.2.6", "vue": "^2.2.6",
"vue-loader": "^11.3.4", "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 ...@@ -5,29 +5,30 @@ module RuboCop
module Migration module Migration
# Cop that checks if `add_column_with_default` is used with `up`/`down` methods # Cop that checks if `add_column_with_default` is used with `up`/`down` methods
# and not `change`. # and not `change`.
class AddColumnWithDefault < RuboCop::Cop::Cop class ReversibleAddColumnWithDefault < RuboCop::Cop::Cop
include MigrationHelpers 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 ' \ 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 'the `up` and `down` methods in your migration class, using `remove_column` in `down`'.freeze
def on_send(node) def on_send(node)
return unless in_migration?(node) return unless in_migration?(node)
return unless add_column_with_default?(node)
name = node.children[1]
return unless name == :add_column_with_default
node.each_ancestor(:def) do |def_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) add_offense(def_node, :name)
end end
end end
def method_name(node)
node.children.first
end
end end
end end
end end
......
require_relative 'cop/custom_error_class' require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher' require_relative 'cop/gem_fetcher'
require_relative 'cop/migration/add_column' 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_foreign_key'
require_relative 'cop/migration/add_concurrent_index' require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index' require_relative 'cop/migration/add_index'
require_relative 'cop/migration/remove_concurrent_index' require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index' require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default'
...@@ -21,4 +21,3 @@ fi ...@@ -21,4 +21,3 @@ fi
echo "✔ Linting passed" echo "✔ Linting passed"
exit 0 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 ...@@ -32,6 +32,10 @@ FactoryGirl.define do
request_access_enabled true request_access_enabled true
end end
trait :with_avatar do
avatar { File.open(Rails.root.join('spec/fixtures/dk.png')) }
end
trait :repository do trait :repository do
# no-op... for now! # no-op... for now!
end 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' require 'spec_helper'
describe 'Explore Groups page', js: true, feature: true do describe 'Explore Groups page', :js, :feature do
let!(:user) { create :user } let!(:user) { create :user }
let!(:group) { create(:group) } let!(:group) { create(:group) }
let!(:public_group) { create(:group, :public) } let!(:public_group) { create(:group, :public) }
...@@ -46,19 +46,39 @@ describe 'Explore Groups page', js: true, feature: true do ...@@ -46,19 +46,39 @@ describe 'Explore Groups page', js: true, feature: true do
it 'shows non-archived projects count' do it 'shows non-archived projects count' do
# Initially project is not archived # Initially project is not archived
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")
# Archive project # Archive project
empty_project.archive! empty_project.archive!
visit explore_groups_path visit explore_groups_path
# Check project count # Check project count
expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("0") expect(find('.js-groups-list-holder .content-list li:first-child .stats span:first-child')).to have_text("0")
# Unarchive project # Unarchive project
empty_project.unarchive! empty_project.unarchive!
visit explore_groups_path visit explore_groups_path
# Check project count # 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
end end
...@@ -20,6 +20,7 @@ feature 'Create New Merge Request', feature: true, js: true do ...@@ -20,6 +20,7 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content('Target branch') expect(page).to have_content('Target branch')
first('.js-source-branch').click first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content')
find('.dropdown-source-branch .dropdown-content a', match: :first).click find('.dropdown-source-branch .dropdown-content a', match: :first).click
expect(page).to have_content "b83d6e3" expect(page).to have_content "b83d6e3"
...@@ -34,6 +35,7 @@ feature 'Create New Merge Request', feature: true, js: true do ...@@ -34,6 +35,7 @@ feature 'Create New Merge Request', feature: true, js: true do
expect(page).to have_content('Target branch') expect(page).to have_content('Target branch')
first('.js-target-branch').click first('.js-target-branch').click
first('.dropdown-target-branch .dropdown-content')
first('.dropdown-target-branch .dropdown-content a', text: 'v1.1.0').click first('.dropdown-target-branch .dropdown-content a', text: 'v1.1.0').click
expect(page).to have_content "b83d6e3" expect(page).to have_content "b83d6e3"
......
require 'spec_helper' require 'spec_helper'
feature 'File blob', :js, feature: true do feature 'File blob', :js, feature: true do
include TreeHelper
include WaitForAjax
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
def visit_blob(path, fragment = nil) 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 end
context 'Ruby file' do context 'Ruby file' do
...@@ -27,6 +24,9 @@ feature 'File blob', :js, feature: true do ...@@ -27,6 +24,9 @@ feature 'File blob', :js, feature: true do
# shows an enabled copy button # shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') 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 end
end end
...@@ -39,7 +39,7 @@ feature 'File blob', :js, feature: true do ...@@ -39,7 +39,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the rich viewer' do
aggregate_failures do aggregate_failures do
# hides the simple viewer # 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="simple"]', visible: false)
...@@ -53,6 +53,9 @@ feature 'File blob', :js, feature: true do ...@@ -53,6 +53,9 @@ feature 'File blob', :js, feature: true do
# shows a disabled copy button # shows a disabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn.disabled') expect(page).to have_selector('.js-copy-blob-source-btn.disabled')
# shows a raw button
expect(page).to have_link('Open raw')
end end
end end
...@@ -63,7 +66,7 @@ feature 'File blob', :js, feature: true do ...@@ -63,7 +66,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the simple viewer' do
aggregate_failures do aggregate_failures do
# hides the rich viewer # hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]') expect(page).to have_selector('.blob-viewer[data-type="simple"]')
...@@ -84,7 +87,7 @@ feature 'File blob', :js, feature: true do ...@@ -84,7 +87,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the rich viewer' do
aggregate_failures do aggregate_failures do
# hides the simple viewer # 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="simple"]', visible: false)
...@@ -105,7 +108,7 @@ feature 'File blob', :js, feature: true do ...@@ -105,7 +108,7 @@ feature 'File blob', :js, feature: true do
wait_for_ajax wait_for_ajax
end end
it 'displays the blob' do it 'displays the blob using the simple viewer' do
aggregate_failures do aggregate_failures do
# hides the rich viewer # hides the rich viewer
expect(page).to have_selector('.blob-viewer[data-type="simple"]') expect(page).to have_selector('.blob-viewer[data-type="simple"]')
...@@ -163,6 +166,9 @@ feature 'File blob', :js, feature: true do ...@@ -163,6 +166,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button # does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn') expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a raw button
expect(page).to have_link('Open raw')
end end
end end
...@@ -206,6 +212,9 @@ feature 'File blob', :js, feature: true do ...@@ -206,6 +212,9 @@ feature 'File blob', :js, feature: true do
# shows an enabled copy button # shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') 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 end
end end
...@@ -240,6 +249,9 @@ feature 'File blob', :js, feature: true do ...@@ -240,6 +249,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button # does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn') expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a download button
expect(page).to have_link('Download')
end end
end end
end end
...@@ -265,6 +277,9 @@ feature 'File blob', :js, feature: true do ...@@ -265,6 +277,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button # does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn') expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a download button
expect(page).to have_link('Download')
end end
end end
end end
...@@ -286,6 +301,9 @@ feature 'File blob', :js, feature: true do ...@@ -286,6 +301,9 @@ feature 'File blob', :js, feature: true do
# shows an enabled copy button # shows an enabled copy button
expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') 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 end
end end
...@@ -308,6 +326,9 @@ feature 'File blob', :js, feature: true do ...@@ -308,6 +326,9 @@ feature 'File blob', :js, feature: true do
# does not show a copy button # does not show a copy button
expect(page).not_to have_selector('.js-copy-blob-source-btn') expect(page).not_to have_selector('.js-copy-blob-source-btn')
# shows a download button
expect(page).to have_link('Download')
end end
end end
end end
......
...@@ -74,8 +74,10 @@ describe 'Cherry-pick Commits' do ...@@ -74,8 +74,10 @@ describe 'Cherry-pick Commits' do
wait_for_ajax wait_for_ajax
page.within('#modal-cherry-pick-commit .dropdown-menu .dropdown-content') do page.within('#modal-cherry-pick-commit .dropdown-menu') do
click_link "'test'" find('.dropdown-input input').set('feature')
wait_for_ajax
click_link "feature"
end end
page.within('#modal-cherry-pick-commit') do page.within('#modal-cherry-pick-commit') do
......
...@@ -200,7 +200,7 @@ feature 'Environment', :feature do ...@@ -200,7 +200,7 @@ feature 'Environment', :feature do
end end
scenario 'user deletes the branch with running environment' do 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 remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click } page.within('.js-branch-feature') { find('a.btn-remove').click }
......
...@@ -85,8 +85,8 @@ feature 'Merge Request button', feature: true do ...@@ -85,8 +85,8 @@ feature 'Merge Request button', feature: true do
context 'on branches page' do context 'on branches page' do
it_behaves_like 'Merge request button only shown when allowed' do it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Merge request' } let(:label) { 'Merge request' }
let(:url) { namespace_project_branches_path(project.namespace, project) } let(:url) { namespace_project_branches_path(project.namespace, project, search: 'feature') }
let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) } let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project, search: 'feature') }
end end
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' require 'rails_helper'
feature 'Create Snippet', feature: true do feature 'Create Snippet', :js, feature: true do
before do before do
login_as :user login_as :user
visit new_snippet_path visit new_snippet_path
...@@ -9,10 +9,11 @@ feature 'Create Snippet', feature: true do ...@@ -9,10 +9,11 @@ feature 'Create Snippet', feature: true do
scenario 'Authenticated user creates a snippet' do scenario 'Authenticated user creates a snippet' do
fill_in 'personal_snippet_title', with: 'My Snippet Title' fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!' find('.ace_editor').native.send_keys 'Hello World!'
end end
click_button 'Create snippet' click_button 'Create snippet'
wait_for_ajax
expect(page).to have_content('My Snippet Title') expect(page).to have_content('My Snippet Title')
expect(page).to have_content('Hello World!') expect(page).to have_content('Hello World!')
...@@ -22,10 +23,11 @@ feature 'Create Snippet', feature: true do ...@@ -22,10 +23,11 @@ feature 'Create Snippet', feature: true do
fill_in 'personal_snippet_title', with: 'My Snippet Title' fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name' 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 end
click_button 'Create snippet' click_button 'Create snippet'
wait_for_ajax
expect(page).to have_content('My Snippet Title') expect(page).to have_content('My Snippet Title')
expect(page).to have_content('snippet+file+name') expect(page).to have_content('snippet+file+name')
......
require 'rails_helper' require 'rails_helper'
feature 'Public Snippets', feature: true do feature 'Public Snippets', :js, feature: true do
scenario 'Unauthenticated user should see public snippets' do scenario 'Unauthenticated user should see public snippets' do
public_snippet = create(:personal_snippet, :public) public_snippet = create(:personal_snippet, :public)
visit snippet_path(public_snippet) visit snippet_path(public_snippet)
wait_for_ajax
expect(page).to have_content(public_snippet.content) expect(page).to have_content(public_snippet.content)
end 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 ...@@ -157,6 +157,7 @@ describe BlobHelper do
describe '#blob_render_error_options' do describe '#blob_render_error_options' do
before do before do
assign(:project, project) assign(:project, project)
assign(:blob, blob)
assign(:id, File.join('master', blob.path)) assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob' controller.params[:controller] = 'projects/blob'
......
/* eslint-disable import/no-unresolved */
import renderPDF from '~/blob/pdf'; import renderPDF from '~/blob/pdf';
import testPDF from './test.pdf'; import testPDF from '../../fixtures/blob/pdf/test.pdf';
describe('PDF renderer', () => { describe('PDF renderer', () => {
let viewer; let viewer;
...@@ -59,7 +61,7 @@ describe('PDF renderer', () => { ...@@ -59,7 +61,7 @@ describe('PDF renderer', () => {
describe('error getting file', () => { describe('error getting file', () => {
beforeEach((done) => { beforeEach((done) => {
viewer.dataset.endpoint = 'invalid/endpoint'; viewer.dataset.endpoint = 'invalid/path/to/file.pdf';
app = renderPDF(); app = renderPDF();
checkLoaded(done); checkLoaded(done);
......
#blob-content-holder .file-holder
.file-content .file-content
.line-numbers .line-numbers
- 1.upto(25) do |i| - 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 ...@@ -7,9 +7,17 @@ describe Gitlab::Email::Receiver, lib: true do
context "when we cannot find a capable handler" do context "when we cannot find a capable handler" do
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") } 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) expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end 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 end
context "when the email is blank" do 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 ...@@ -87,10 +87,10 @@ describe Gitlab::UserAccess, lib: true do
expect(access.can_push_to_branch?(branch.name)).to be_falsey expect(access.can_push_to_branch?(branch.name)).to be_falsey
end 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] 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
end end
......
...@@ -781,17 +781,14 @@ describe Project, models: true do ...@@ -781,17 +781,14 @@ describe Project, models: true do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
context 'When avatar file is uploaded' do context 'when avatar file is uploaded' do
before do let(:project) { create(:empty_project, :with_avatar) }
project.update_columns(avatar: 'uploads/avatar.png')
allow(project.avatar).to receive(:present?) { true }
end
let(:avatar_path) do it 'creates a correct avatar path' do
"/uploads/project/avatar/#{project.id}/uploads/avatar.png" avatar_path = "/uploads/project/avatar/#{project.id}/dk.png"
end
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 end
context 'When avatar file in git' do context 'When avatar file in git' do
......
...@@ -1849,17 +1849,15 @@ describe Repository, models: true do ...@@ -1849,17 +1849,15 @@ describe Repository, models: true do
end end
end end
# TODO: Uncomment when feature is reenabled describe '#is_ancestor?' do
# describe '#is_ancestor?' do context 'Gitaly is_ancestor feature enabled' do
# context 'Gitaly is_ancestor feature enabled' do it "asks Gitaly server if it's an ancestor" do
# it 'asks Gitaly server if it\'s an ancestor' do commit = repository.commit
# 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) 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
# expect(repository.is_ancestor?(commit.id, commit.id)).to be true end
# end end
# 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 ...@@ -5,7 +5,6 @@ describe Snippet, models: true do
subject { described_class } subject { described_class }
it { is_expected.to include_module(Gitlab::VisibilityLevel) } 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(Participable) }
it { is_expected.to include_module(Referable) } it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) } it { is_expected.to include_module(Sortable) }
...@@ -241,4 +240,16 @@ describe Snippet, models: true do ...@@ -241,4 +240,16 @@ describe Snippet, models: true do
end end
end 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 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' ...@@ -3,9 +3,9 @@ require 'spec_helper'
require 'rubocop' require 'rubocop'
require 'rubocop/rspec/support' 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 include CopHelper
subject(:cop) { described_class.new } subject(:cop) { described_class.new }
......
...@@ -40,7 +40,8 @@ module TestEnv ...@@ -40,7 +40,8 @@ module TestEnv
'wip' => 'b9238ee', 'wip' => 'b9238ee',
'csv' => '3dd0896', 'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3', 'v1.1.0' => 'b83d6e3',
'add-ipython-files' => '6d85bb69' 'add-ipython-files' => '6d85bb69',
'add-pdf-file' => 'e774ebd3'
}.freeze }.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily # 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 ...@@ -21,6 +21,7 @@ describe 'projects/blob/_viewer.html.haml', :view do
before do before do
assign(:project, project) assign(:project, project)
assign(:blob, blob)
assign(:id, File.join('master', blob.path)) assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob' controller.params[:controller] = 'projects/blob'
......
...@@ -34,12 +34,14 @@ describe ExpireBuildInstanceArtifactsWorker do ...@@ -34,12 +34,14 @@ describe ExpireBuildInstanceArtifactsWorker do
context 'when associated project was removed' do context 'when associated project was removed' do
let(:build) do let(:build) do
create(:ci_build, :artifacts, artifacts_expiry) do |build| create(:ci_build, :artifacts, artifacts_expiry) do |build|
build.project.delete build.project.pending_delete = true
end end
end end
it 'does not remove artifacts' do 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 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: ...@@ -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: dependencies:
mime-db "~1.26.0" 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" version "1.3.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
...@@ -3710,6 +3710,10 @@ nested-error-stacks@^1.0.0: ...@@ -3710,6 +3710,10 @@ nested-error-stacks@^1.0.0:
dependencies: dependencies:
inherits "~2.0.1" 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: node-libs-browser@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-1.1.1.tgz#2a38243abedd7dffcd07a97c9aca5668975a6fea" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-1.1.1.tgz#2a38243abedd7dffcd07a97c9aca5668975a6fea"
...@@ -4102,6 +4106,13 @@ pbkdf2@^3.0.3: ...@@ -4102,6 +4106,13 @@ pbkdf2@^3.0.3:
dependencies: dependencies:
create-hmac "^1.1.2" 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: pend@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
...@@ -5542,6 +5553,13 @@ update-notifier@0.5.0: ...@@ -5542,6 +5553,13 @@ update-notifier@0.5.0:
semver-diff "^2.0.0" semver-diff "^2.0.0"
string-length "^1.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: url-parse@1.0.x:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
...@@ -5825,6 +5843,12 @@ wordwrap@~0.0.2: ...@@ -5825,6 +5843,12 @@ wordwrap@~0.0.2:
version "0.0.3" version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" 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: wrap-ansi@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" 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