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