Commit fe132bf3 authored by Marcia Ramos's avatar Marcia Ramos

fix conflict

parents c7a8b1c8 23bf469a
......@@ -227,7 +227,7 @@ GEM
railties (>= 3.0.0)
faraday (0.12.2)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.11.0.1)
faraday_middleware (0.12.2)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.7)
aws-sdk-resources (~> 2)
......@@ -619,7 +619,7 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
parser (2.5.0.5)
parser (2.5.1.0)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
......
import initNotes from '~/init_notes';
import ZenMode from '~/zen_mode';
import LineHighlighter from '../../../../line_highlighter';
import BlobViewer from '../../../../blob/viewer';
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
});
import LineHighlighter from '../../../line_highlighter';
import BlobViewer from '../../../blob/viewer';
import ZenMode from '../../../zen_mode';
import initNotes from '../../../init_notes';
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import ZenMode from '~/zen_mode';
import initNotes from '~/init_notes';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
});
<script>
import $ from 'jquery';
/**
* Renders each stage of the pipeline mini graph.
......@@ -13,8 +12,11 @@
* 3. Merge request widget
* 4. Commit widget
*/
import axios from '../../lib/utils/axios_utils';
import $ from 'jquery';
import Flash from '../../flash';
import axios from '../../lib/utils/axios_utils';
import eventHub from '../event_hub';
import Icon from '../../vue_shared/components/icon.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
......@@ -82,6 +84,7 @@
methods: {
onClickStage() {
if (!this.isDropdownOpen()) {
eventHub.$emit('clickedDropdown');
this.isLoading = true;
this.fetchJobs();
}
......
// eslint-disable-next-line import/prefer-default-export
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
......@@ -7,6 +7,7 @@ import SvgBlankState from '../components/blank_state.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
export default {
components: {
......@@ -52,34 +53,58 @@ export default {
});
eventHub.$on('postAction', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
},
destroyed() {
this.poll.stop();
},
methods: {
updateTable() {
// Cancel ongoing request
if (this.isMakingRequest) {
this.service.cancelationSource.cancel(CANCEL_REQUEST);
}
// Stop polling
this.poll.stop();
// Update the table
return this.getPipelines()
.then(() => this.poll.restart());
},
fetchPipelines() {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
this.getPipelines();
}
},
getPipelines() {
return this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch((error) => this.errorCallback(error));
},
setCommonData(pipelines) {
this.store.storePipelines(pipelines);
this.isLoading = false;
this.updateGraphDropdown = true;
this.hasMadeRequest = true;
// In case the previous polling request returned an error, we need to reset it
if (this.hasError) {
this.hasError = false;
}
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
this.updateGraphDropdown = false;
errorCallback(error) {
this.hasMadeRequest = true;
this.isLoading = false;
if (error && error.message && error.message !== CANCEL_REQUEST) {
this.hasError = true;
this.updateGraphDropdown = false;
}
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
......
......@@ -19,8 +19,13 @@ export default class PipelinesService {
getPipelines(data = {}) {
const { scope, page } = data;
const CancelToken = axios.CancelToken;
this.cancelationSource = CancelToken.source();
return axios.get(this.endpoint, {
params: { scope, page },
cancelToken: this.cancelationSource.token,
});
}
......
import { visitUrl } from './lib/utils/url_utility';
/**
* Helper function that finds the href of the fiven selector and updates the location.
*
* @param {String} selector
*/
export default (selector) => {
const link = document.querySelector(selector).getAttribute('href');
export default function findAndFollowLink(selector) {
const element = document.querySelector(selector);
const link = element && element.getAttribute('href');
if (link) {
window.location = link;
visitUrl(link);
}
};
}
export default () => {
const { protocol, host, pathname } = location;
const shareBtn = document.querySelector('.js-share-btn');
const embedBtn = document.querySelector('.js-embed-btn');
const snippetUrlArea = document.querySelector('.js-snippet-url-area');
const embedAction = document.querySelector('.js-embed-action');
const url = `${protocol}//${host + pathname}`;
shareBtn.addEventListener('click', () => {
shareBtn.classList.add('is-active');
embedBtn.classList.remove('is-active');
snippetUrlArea.value = url;
embedAction.innerText = 'Share';
});
embedBtn.addEventListener('click', () => {
embedBtn.classList.add('is-active');
shareBtn.classList.remove('is-active');
const scriptTag = `<script src="${url}.js"></script>`;
snippetUrlArea.value = scriptTag;
embedAction.innerText = 'Embed';
});
};
......@@ -9,7 +9,7 @@
lines: {
type: Number,
required: false,
default: 6,
default: 3,
},
},
computed: {
......
......@@ -37,7 +37,11 @@
/*
* Code highlight
*/
@import "highlight/**/*";
@import "highlight/dark";
@import "highlight/monokai";
@import "highlight/solarized_dark";
@import "highlight/solarized_light";
@import "highlight/white";
/*
* Styles for JS behaviors.
......
......@@ -187,12 +187,9 @@ a {
animation: fadeInFull $fade-in-duration 1;
}
.animation-container {
background: $repo-editor-grey;
height: 40px;
overflow: hidden;
position: relative;
&.animation-container-small {
height: 12px;
......@@ -205,60 +202,43 @@ a {
}
}
&::before {
animation-duration: 1s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-name: blockTextShine;
animation-timing-function: linear;
background-image: $repo-editor-linear-gradient;
background-repeat: no-repeat;
background-size: 800px 45px;
content: ' ';
display: block;
height: 100%;
[class^="skeleton-line-"] {
position: relative;
}
div {
background: $white-light;
height: 6px;
left: 0;
position: absolute;
right: 0;
}
.skeleton-line-1 {
left: 0;
top: 8px;
}
.skeleton-line-2 {
left: 150px;
top: 0;
background-color: $theme-gray-100;
height: 10px;
}
overflow: hidden;
.skeleton-line-3 {
left: 0;
top: 23px;
}
&:not(:last-of-type) {
margin-bottom: 4px;
}
.skeleton-line-4 {
left: 0;
top: 38px;
&::after {
content: ' ';
display: block;
animation: blockTextShine 1s linear infinite forwards;
background-repeat: no-repeat;
background-size: cover;
background-image: linear-gradient(
to right,
$theme-gray-100 0%,
$theme-gray-50 20%,
$theme-gray-100 40%,
$theme-gray-100 100%
);
height: 10px;
}
}
}
.skeleton-line-5 {
left: 200px;
top: 28px;
height: 10px;
}
$skeleton-line-widths: (
156px,
235px,
200px,
);
.skeleton-line-6 {
top: 14px;
left: 230px;
height: 10px;
@for $count from 1 through length($skeleton-line-widths) {
.skeleton-line-#{$count} {
width: nth($skeleton-line-widths, $count);
}
}
......
......@@ -481,7 +481,8 @@
.dropdown-menu-selectable {
li {
a {
a,
button {
padding: 8px 40px;
position: relative;
......
......@@ -29,8 +29,10 @@
}
.snippet-title {
font-size: 24px;
color: $gl-text-color;
font-size: 2em;
font-weight: $gl-font-weight-bold;
min-height: $header-height;
}
.snippet-edited-ago {
......@@ -46,3 +48,26 @@
.snippet-scope-menu .btn-new {
margin-top: 15px;
}
.snippet-embed-input {
height: 35px;
}
.embed-snippet {
padding-right: 0;
padding-top: $gl-padding;
.form-control {
cursor: auto;
width: 101%;
margin-left: -1px;
}
.embed-toggle-list li button {
padding: 8px 40px;
}
.embed-toggle {
height: 35px;
}
}
......@@ -737,20 +737,6 @@ $color-high-score: $green-400;
$color-average-score: $orange-400;
$color-low-score: $red-400;
/*
Repo editor
*/
$repo-editor-grey: #f6f7f9;
$repo-editor-grey-darker: #e9ebee;
$repo-editor-linear-gradient: linear-gradient(
to right,
$repo-editor-grey 0%,
$repo-editor-grey-darker,
20%,
$repo-editor-grey 40%,
$repo-editor-grey 100%
);
/*
Performance Bar
*/
......
/* https://github.com/aahan/pygments-github-style */
/*
* White Syntax Colors
*/
$white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
$white-cm: #998;
$white-cp: #999;
$white-c1: #998;
$white-cs: #999;
$white-gd: $black;
$white-gd-bg: #fdd;
$white-gd-x: $black;
$white-gd-x-bg: #faa;
$white-gr: #a00;
$white-gh: #999;
$white-gi: $black;
$white-gi-bg: #dfd;
$white-gi-x: $black;
$white-gi-x-bg: #afa;
$white-go: #888;
$white-gp: #555;
$white-gu: #800080;
$white-gt: #a00;
$white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
$white-na: teal;
$white-nb: #0086b3;
$white-nc: #458;
$white-no: teal;
$white-ni: purple;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
$white-nt: navy;
$white-nv: teal;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
$white-mi: #099;
$white-mo: #099;
$white-sb: #d14;
$white-sc: #d14;
$white-sd: #d14;
$white-s2: #d14;
$white-se: #d14;
$white-sh: #d14;
$white-si: #d14;
$white-sx: #d14;
$white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
$white-vc: teal;
$white-vg: teal;
$white-vi: teal;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
@mixin matchLine {
color: $black-transparent;
background-color: $gray-light;
}
.code.white {
// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
}
.diff-line-num,
.diff-line-num a {
color: $black-transparent;
}
// Code itself
pre.code,
.diff-line-num {
border-color: $white-normal;
}
&,
pre.code,
.line_holder .line_content {
background-color: $white-light;
color: $white-code-color;
}
// Diff line
.line_holder {
&.match .line_content {
@include matchLine;
}
.diff-line-num {
&.old {
background-color: $line-number-old;
border-color: $line-removed-dark;
a {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.new {
background-color: $line-number-new;
border-color: $line-added-dark;
a {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
}
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
}
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content {
&.old {
background-color: $line-removed;
&::before {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-removed-dark;
}
}
&.new {
background-color: $line-added;
&::before {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-added-dark;
}
}
&.match {
@include matchLine;
}
&.hll:not(.empty-cell) {
background-color: $line-select-yellow;
}
}
}
// highlight line via anchor
pre .hll {
background-color: $white-pre-hll-bg !important;
}
// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
.c { color: $white-c; font-style: italic; }
.err { color: $white-err; background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
.cm { color: $white-cm; font-style: italic; }
.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1; font-style: italic; }
.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
.gd {
color: $white-gd;
background-color: $white-gd-bg;
.x {
color: $white-gd-x;
background-color: $white-gd-x-bg;
}
}
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
.gi {
color: $white-gi;
background-color: $white-gi-bg;
.x {
color: $white-gi-x;
background-color: $white-gi-x-bg;
}
}
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
.ow { font-weight: $gl-font-weight-bold; }
.w { color: $white-w; }
.mf { color: $white-mf; }
.mh { color: $white-mh; }
.mi { color: $white-mi; }
.mo { color: $white-mo; }
.sb { color: $white-sb; }
.sc { color: $white-sc; }
.sd { color: $white-sd; }
.s2 { color: $white-s2; }
.se { color: $white-se; }
.sh { color: $white-sh; }
.si { color: $white-si; }
.sx { color: $white-sx; }
.sr { color: $white-sr; }
.s1 { color: $white-s1; }
.ss { color: $white-ss; }
.bp { color: $white-bp; }
.vc { color: $white-vc; }
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
.gc { color: $white-gc-color; background-color: $white-gc-bg; }
@import "white_base";
}
/* https://github.com/aahan/pygments-github-style */
/*
* White Syntax Colors
*/
$white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
$white-cm: #998;
$white-cp: #999;
$white-c1: #998;
$white-cs: #999;
$white-gd: $black;
$white-gd-bg: #fdd;
$white-gd-x: $black;
$white-gd-x-bg: #faa;
$white-gr: #a00;
$white-gh: #999;
$white-gi: $black;
$white-gi-bg: #dfd;
$white-gi-x: $black;
$white-gi-x-bg: #afa;
$white-go: #888;
$white-gp: #555;
$white-gu: #800080;
$white-gt: #a00;
$white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
$white-na: teal;
$white-nb: #0086b3;
$white-nc: #458;
$white-no: teal;
$white-ni: purple;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
$white-nt: navy;
$white-nv: teal;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
$white-mi: #099;
$white-mo: #099;
$white-sb: #d14;
$white-sc: #d14;
$white-sd: #d14;
$white-s2: #d14;
$white-se: #d14;
$white-sh: #d14;
$white-si: #d14;
$white-sx: #d14;
$white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
$white-vc: teal;
$white-vg: teal;
$white-vi: teal;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
@mixin matchLine {
color: $black-transparent;
background-color: $gray-light;
}
// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
}
.diff-line-num,
.diff-line-num a {
color: $black-transparent;
}
// Code itself
pre.code,
.diff-line-num {
border-color: $white-normal;
}
&,
pre.code,
.line_holder .line_content {
background-color: $white-light;
color: $white-code-color;
}
// Diff line
.line_holder {
&.match .line_content {
@include matchLine;
}
.diff-line-num {
&.old {
background-color: $line-number-old;
border-color: $line-removed-dark;
a {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.new {
background-color: $line-number-new;
border-color: $line-added-dark;
a {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
}
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
}
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content {
&.old {
background-color: $line-removed;
&::before {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-removed-dark;
}
}
&.new {
background-color: $line-added;
&::before {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-added-dark;
}
}
&.match {
@include matchLine;
}
&.hll:not(.empty-cell) {
background-color: $line-select-yellow;
}
}
}
// highlight line via anchor
pre .hll {
background-color: $white-pre-hll-bg !important;
}
// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
.c { color: $white-c; font-style: italic; }
.err { color: $white-err; background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
.cm { color: $white-cm; font-style: italic; }
.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1; font-style: italic; }
.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
.gd {
color: $white-gd;
background-color: $white-gd-bg;
.x {
color: $white-gd-x;
background-color: $white-gd-x-bg;
}
}
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
.gi {
color: $white-gi;
background-color: $white-gi-bg;
.x {
color: $white-gi-x;
background-color: $white-gi-x-bg;
}
}
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
.ow { font-weight: $gl-font-weight-bold; }
.w { color: $white-w; }
.mf { color: $white-mf; }
.mh { color: $white-mh; }
.mi { color: $white-mi; }
.mo { color: $white-mo; }
.sb { color: $white-sb; }
.sc { color: $white-sc; }
.sd { color: $white-sd; }
.s2 { color: $white-s2; }
.se { color: $white-se; }
.sh { color: $white-sh; }
.si { color: $white-si; }
.sx { color: $white-sx; }
.sr { color: $white-sr; }
.s1 { color: $white-s1; }
.ss { color: $white-ss; }
.bp { color: $white-bp; }
.vc { color: $white-vc; }
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
.gc { color: $white-gc-color; background-color: $white-gc-bg; }
......@@ -195,7 +195,6 @@
background-color: $white-light;
}
.milestone-deprecation-message {
.popover {
padding: 0;
......
@import "framework/variables";
.gitlab-embed-snippets {
@import "highlight/embedded";
@import "framework/images";
$border-style: 1px solid $border-color;
font-family: $regular_font;
font-size: $gl-font-size;
line-height: $code_line_height;
color: $gl-text-color;
margin: 20px;
font-weight: 200;
.gl-snippet-icon {
display: inline-block;
background: url(asset_path('ext_snippet_icons/ext_snippet_icons.png')) no-repeat;
overflow: hidden;
text-align: left;
width: 16px;
height: 16px;
background-size: cover;
&.gl-snippet-icon-doc_code { background-position: 0 0; }
&.gl-snippet-icon-doc_text { background-position: 0 -16px; }
&.gl-snippet-icon-download { background-position: 0 -32px; }
}
.blob-viewer {
background-color: $white-light;
text-align: left;
}
.file-content.code {
border: $border-style;
border-radius: 0 0 4px 4px;
display: flex;
box-shadow: none;
margin: 0;
padding: 0;
table-layout: fixed;
.blob-content {
overflow-x: auto;
pre {
padding: 10px;
border: 0;
border-radius: 0;
font-family: $monospace_font;
font-size: $code_font_size;
line-height: $code_line_height;
margin: 0;
overflow: auto;
overflow-y: hidden;
white-space: pre;
word-wrap: normal;
border-left: $border-style;
}
}
.line-numbers {
padding: 10px;
text-align: right;
float: left;
.diff-line-num {
font-family: $monospace_font;
display: block;
font-size: $code_font_size;
min-height: $code_line_height;
white-space: nowrap;
color: $black-transparent;
min-width: 30px;
}
.diff-line-num:hover {
color: $almost-black;
cursor: pointer;
}
}
}
.file-title-flex-parent {
display: flex;
align-items: center;
justify-content: space-between;
background-color: $gray-light;
border: $border-style;
border-bottom: 0;
padding: $gl-padding-top $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
.file-header-content {
.file-title-name {
font-weight: $gl-font-weight-bold;
}
.gitlab-embedded-snippets-title {
text-decoration: none;
color: $gl-text-color;
&:hover {
text-decoration: underline;
}
}
.gitlab-logo {
display: inline-block;
padding-left: 5px;
text-decoration: none;
color: $gl-text-color-secondary;
.logo-text {
background: image_url('ext_snippet_icons/logo.png') no-repeat left center;
background-size: 18px;
font-weight: $gl-font-weight-normal;
padding-left: 24px;
}
}
}
img,
.gl-snippet-icon {
display: inline-block;
vertical-align: middle;
}
}
.btn-group {
a.btn {
background-color: $white-light;
text-decoration: none;
padding: 7px 9px;
border: $border-style;
border-right: 0;
&:hover {
background-color: $white-normal;
border-color: $border-white-normal;
text-decoration: none;
}
&:first-child {
border-radius: 3px 0 0 3px;
}
&:last-child {
border-radius: 0 3px 3px 0;
border-right: $border-style;
}
}
}
}
......@@ -17,6 +17,10 @@ module SnippetsActions
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def js_request?
request.format.js?
end
private
def convert_line_endings(content)
......
......@@ -5,6 +5,8 @@ class Projects::SnippetsController < Projects::ApplicationController
include SnippetsActions
include RendersBlob
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
......@@ -71,6 +73,7 @@ class Projects::SnippetsController < Projects::ApplicationController
format.json do
render_blob_json(blob)
end
format.js { render 'shared/snippets/show'}
end
end
......
......@@ -6,6 +6,8 @@ class SnippetsController < ApplicationController
include RendersBlob
include PreviewMarkdown
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
......@@ -77,6 +79,8 @@ class SnippetsController < ApplicationController
format.json do
render_blob_json(blob)
end
format.js { render 'shared/snippets/show' }
end
end
......
......@@ -43,6 +43,10 @@ module IconsHelper
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
end
def external_snippet_icon(name)
content_tag(:span, "", class: "gl-snippet-icon gl-snippet-icon-#{name}")
end
def audit_icon(names, options = {})
case names
when "standard"
......
......@@ -101,4 +101,39 @@ module SnippetsHelper
# Return snippet with chunk array
{ snippet_object: snippet, snippet_chunks: snippet_chunks }
end
def snippet_embed
"<script src=\"#{url_for(only_path: false, overwrite_params: nil)}.js\"></script>"
end
def embedded_snippet_raw_button
blob = @snippet.blob
return if blob.empty? || blob.raw_binary? || blob.stored_externally?
snippet_raw_url = if @snippet.is_a?(PersonalSnippet)
raw_snippet_url(@snippet)
else
raw_project_snippet_url(@snippet.project, @snippet)
end
link_to external_snippet_icon('doc_code'), snippet_raw_url, class: 'btn', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw'
end
def embedded_snippet_download_button
download_url = if @snippet.is_a?(PersonalSnippet)
raw_snippet_url(@snippet, inline: false)
else
raw_project_snippet_url(@snippet.project, @snippet, inline: false)
end
link_to external_snippet_icon('download'), download_url, class: 'btn', target: '_blank', title: 'Download', rel: 'noopener noreferrer'
end
def public_snippet?
if @snippet.project_id?
can?(nil, :read_project_snippet, @snippet)
else
can?(nil, :read_personal_snippet, @snippet)
end
end
end
......@@ -23,9 +23,12 @@ class InternalId < ActiveRecord::Base
#
# The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL).
# As such, the increment is atomic and safe to be called concurrently.
def increment_and_save!
#
# If a `maximum_iid` is passed in, this overrides the incremented value if it's
# greater than that. This can be used to correct the increment value if necessary.
def increment_and_save!(maximum_iid)
lock!
self.last_value = (last_value || 0) + 1
self.last_value = [(last_value || 0) + 1, (maximum_iid || 0) + 1].max
save!
last_value
end
......@@ -89,7 +92,16 @@ class InternalId < ActiveRecord::Base
# and increment its last value
#
# Note this will acquire a ROW SHARE lock on the InternalId record
(lookup || create_record).increment_and_save!
# Note we always calculate the maximum iid present here and
# pass it in to correct the InternalId entry if it's last_value is off.
#
# This can happen in a transition phase where both `AtomicInternalId` and
# `NonatomicInternalId` code runs (e.g. during a deploy).
#
# This is subject to be cleaned up with the 10.8 release:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/45389.
(lookup || create_record).increment_and_save!(maximum_iid)
end
end
......@@ -115,11 +127,15 @@ class InternalId < ActiveRecord::Base
InternalId.create!(
**scope,
usage: usage_value,
last_value: init.call(subject) || 0
last_value: maximum_iid
)
end
rescue ActiveRecord::RecordNotUnique
lookup
end
def maximum_iid
@maximum_iid ||= init.call(subject) || 0
end
end
end
......@@ -144,6 +144,7 @@
GitLab
%span.pull-right
= Gitlab::VERSION
= "(#{Gitlab::REVISION})"
%p
GitLab Shell
%span.pull-right
......
......@@ -20,7 +20,7 @@
- else
%p
Download the Google Authenticator application from App Store or Google Play Store and scan this code.
More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}.
More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}.
.row.append-bottom-10
.col-md-4
= raw @qr_code
......
......@@ -2,6 +2,7 @@
- render_error = viewer.render_error
- rich_type = viewer.type == :rich ? viewer.partial_name : nil
- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?)
- external_embed = local_assigns.fetch(:external_embed, false)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
.blob-viewer{ data: { type: viewer.type, rich_type: rich_type, url: viewer_url }, class: ('hidden' if hidden) }
......@@ -9,6 +10,8 @@
= render 'projects/blob/render_error', viewer: viewer
- elsif load_async
= render viewer.loading_partial_path, viewer: viewer
- elsif external_embed
= render 'projects/blob/viewers/highlight_embed', blob: viewer.blob
- else
- viewer.prepare!
......
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
- blob.data.each_line.each_with_index do |_, index|
%span.diff-line-num= index + 1
.blob-content{ data: { blob_id: blob.id } }
= highlight(blob.path, blob.data, repository: nil, plain: blob.no_highlighting?)
- blob = @snippet.blob
.gitlab-embed-snippets
.js-file-title.file-title-flex-parent
.file-header-content
= external_snippet_icon('doc_text')
%strong.file-title-name
%a.gitlab-embedded-snippets-title{ href: url_for(only_path: false, overwrite_params: nil) }
= blob.name
%small
= number_to_human_size(blob.raw_size)
%a.gitlab-logo{ href: url_for(only_path: false, overwrite_params: nil), title: 'view on gitlab' }
on &nbsp;
%span.logo-text
GitLab
.file-actions.hidden-xs
.btn-group{ role: "group" }<
= embedded_snippet_raw_button
= embedded_snippet_download_button
%article.file-holder.snippet-file-content
= render 'projects/blob/viewer', viewer: @snippet.blob.simple_viewer, load_async: false, external_embed: true
......@@ -19,11 +19,32 @@
%h2.snippet-title.prepend-top-0.append-bottom-0
= markdown_field(@snippet, :title)
- if @snippet.updated_at != @snippet.created_at
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
- if @snippet.description.present?
.description
.wiki
= markdown_field(@snippet, :description)
%textarea.hidden.js-task-list-field
= @snippet.description
- if @snippet.updated_at != @snippet.created_at
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
- if public_snippet?
.embed-snippet
.input-group
.input-group-btn
%button.btn.embed-toggle{ 'data-toggle': 'dropdown', type: 'button' }
%span.js-embed-action= _("Embed")
= sprite_icon('angle-down', size: 12)
%ul.dropdown-menu.dropdown-menu-selectable.embed-toggle-list
%li
%button.js-embed-btn.btn.btn-transparent.is-active{ type: 'button' }
%strong.embed-toggle-list-item= _("Embed")
%li
%button.js-share-btn.btn.btn-transparent{ type: 'button' }
%strong.embed-toggle-list-item= _("Share")
%input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
.input-group-btn
%button.js-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '#snippet-url-area' }
= sprite_icon('duplicate', size: 16)
.clearfix
document.write('#{escape_javascript(stylesheet_link_tag "#{stylesheet_url 'snippets'}")}');
document.write('#{escape_javascript(render 'shared/snippets/embed')}');
---
title: Now `rake cache:clear` will also clear pipeline status cache
merge_request: 18257
author:
type: fixed
---
title: Update faraday_middlewar to 0.12.2
merge_request: 18397
author: Takuya Noguchi
type: security
---
title: Adds Embedded Snippets Support
merge_request: 15695
author: haseebeqx
type: added
---
title: Validate project path prior to hitting the database.
merge_request: 18322
author:
type: performance
---
title: git SHA is now displayed alongside the GitLab version on the Admin Dashboard
merge_request:
author:
type: added
---
title: Replace the `project/commits/comments.feature` spinach test with an rspec analog
merge_request: 18356
author: "@blackst0ne"
type: other
......@@ -127,6 +127,7 @@ module Gitlab
config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "test.css"
config.assets.precompile << "snippets.css"
config.assets.precompile << "locale/**/app.js"
# Import gitlab-svgs directly from vendored directory
......
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
if Gitlab.inc_controlled? || Rails.env.development?
if Gitlab.dev_env_or_com?
ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
end
......@@ -202,7 +202,7 @@ instant how code changes impact your production environment.
### Git and GitLab
- [Git](topics/git/index.md): Getting started with Git, branching strategies, Git LFS, advanced use.
- [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf): Download a PDF describing the most used Git operations.
- [Git cheatsheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf): Download a PDF describing the most used Git operations.
- [GitLab Flow](workflow/gitlab_flow.md): explore the best of Git with the GitLab Flow strategy.
## Administrator documentation
......
......@@ -15,7 +15,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, or `approval_required`. |
| `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, `approval_required`, `unmergeable` or `directly_addressed`. |
| `author_id` | integer | no | The ID of an author |
| `project_id` | integer | no | The ID of a project |
| `state` | string | no | The state of the todo. Can be either `pending` or `done` |
......
......@@ -30,6 +30,7 @@ container_scanning:
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- touch clair-whitelist.yml
- while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
- ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
artifacts:
paths: [gl-sast-container-report.json]
......
......@@ -308,7 +308,9 @@ except master.
## `only` and `except` (complex)
> Introduced in GitLab 10.0
> `refs` and `kubernetes` policies introduced in GitLab 10.0
> `variables` policy introduced in 10.7
CAUTION: **Warning:**
This an _alpha_ feature, and it it subject to change at any time without
......
......@@ -15,6 +15,10 @@ GitLab [administrators](../README.md#administrator-documentation) receive all pe
To add or import a user, you can follow the
[project members documentation](../user/project/members/index.md).
## Principles behind permissions
See our [product handbook on permissions](https://about.gitlab.com/handbook/product#permissions-in-gitlab)
## Project members permissions
The following table depicts the various user permission levels in a project.
......
......@@ -28,7 +28,7 @@ integrated platform
permission to create tags, and prevent accidental update or deletion
- [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits
- [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry.
- [Web IDE](web_ide/index.md) **[ULTIMATE]**
- [Web IDE](web_ide/index.md)
**Issues and merge requests:**
......
......@@ -15,8 +15,8 @@ in the table below.
Once you have configured and enabled Custom Issue Tracker Service you'll see a link on the GitLab project pages that takes you to that custom issue tracker.
## Referencing issues
Issues are referenced with `#<ID>`, where `<ID>` is a number (example `#143`).
So with the example above, `#143` would refer to `https://customissuetracker.com/project-name/143`.
\ No newline at end of file
- Issues are referenced with `ANYTHING-<ID>`, where `ANYTHING` can be any string and `<ID>` is a number used in the target project of the custom integration (example `PROJECT-143`).
- `ANYTHING` is a placeholder to differentiate against GitLab issues, which are referenced with `#<ID>`. You can use a project name or project key to replace it for example.
- So with the example above, `PROJECT-143` would refer to `https://customissuetracker.com/project-name/143`.
\ No newline at end of file
@project_commits
Feature: Project Commits Comments
Background:
Given I sign in as a user
And I own project "Shop"
And I visit project commit page
@javascript
Scenario: I can comment on a commit
Given I leave a comment like "XML attached"
Then I should see a comment saying "XML attached"
@javascript
Scenario: I can't cancel the main form
Then I should not see the cancel comment button
@javascript
Scenario: I can preview with text
Given I write a comment like ":+1: Nice"
Then The comment preview tab should be display rendered Markdown
@javascript
Scenario: I preview a comment
Given I preview a comment text like "Bug fixed :smile:"
Then I should see the comment preview
And I should not see the comment text field
@javascript
Scenario: I can edit after preview
Given I preview a comment text like "Bug fixed :smile:"
Then I should see the comment write tab
@javascript
Scenario: I have a reset form after posting from preview
Given I preview a comment text like "Bug fixed :smile:"
And I submit the comment
Then I should see an empty comment text field
And I should not see the comment preview
@javascript
Scenario: I can delete a comment
Given I leave a comment like "XML attached"
Then I should see a comment saying "XML attached"
And I delete a comment
Then I should not see a comment saying "XML attached"
@javascript
Scenario: I can edit a comment with +1
Given I leave a comment like "XML attached"
And I edit the last comment with a +1
Then I should see +1 in the description
......@@ -6,70 +6,12 @@ module SharedNote
wait_for_requests if javascript_test?
end
step 'I delete a comment' do
page.within('.main-notes-list') do
note = find('.note')
note.hover
find('.more-actions').click
find('.more-actions .dropdown-menu li', match: :first)
accept_confirm { find(".js-note-delete").click }
end
end
step 'I haven\'t written any comment text' do
page.within(".js-main-target-form") do
fill_in "note[note]", with: ""
end
end
step 'I leave a comment like "XML attached"' do
page.within(".js-main-target-form") do
fill_in "note[note]", with: "XML attached"
click_button "Comment"
end
wait_for_requests
end
step 'I preview a comment text like "Bug fixed :smile:"' do
page.within(".js-main-target-form") do
fill_in "note[note]", with: "Bug fixed :smile:"
find('.js-md-preview-button').click
end
end
step 'I submit the comment' do
page.within(".js-main-target-form") do
click_button "Comment"
end
wait_for_requests
end
step 'I write a comment like ":+1: Nice"' do
page.within(".js-main-target-form") do
fill_in 'note[note]', with: ':+1: Nice'
end
end
step 'I should not see a comment saying "XML attached"' do
expect(page).not_to have_css(".note")
end
step 'I should not see the cancel comment button' do
page.within(".js-main-target-form") do
should_not have_link("Cancel")
end
end
step 'I should not see the comment preview' do
page.within(".js-main-target-form") do
expect(find('.js-md-preview')).not_to be_visible
end
end
step 'The comment preview tab should say there is nothing to do' do
page.within(".js-main-target-form") do
find('.js-md-preview-button').click
......@@ -77,71 +19,7 @@ module SharedNote
end
end
step 'I should not see the comment text field' do
page.within(".js-main-target-form") do
expect(find('.js-note-text')).not_to be_visible
end
end
step 'I should see a comment saying "XML attached"' do
page.within(".note") do
expect(page).to have_content("XML attached")
end
end
step 'I should see an empty comment text field' do
page.within(".js-main-target-form") do
expect(page).to have_field("note[note]", with: "")
end
end
step 'I should see the comment write tab' do
page.within(".js-main-target-form") do
expect(page).to have_css('.js-md-write-button', visible: true)
end
end
step 'The comment preview tab should be display rendered Markdown' do
page.within(".js-main-target-form") do
find('.js-md-preview-button').click
expect(find('.js-md-preview')).to have_css('gl-emoji', visible: true)
end
end
step 'I should see the comment preview' do
page.within(".js-main-target-form") do
expect(page).to have_css('.js-md-preview', visible: true)
end
end
step 'I should see no notes at all' do
expect(page).not_to have_css('.note')
end
# Markdown
step 'I edit the last comment with a +1' do
page.within(".main-notes-list") do
note = find('.note')
note.hover
note.find('.js-note-edit').click
end
page.find('.current-note-edit-form textarea')
page.within(".current-note-edit-form") do
fill_in 'note[note]', with: '+1 Awesome!'
click_button 'Save comment'
end
wait_for_requests
end
step 'I should see +1 in the description' do
page.within(".note") do
expect(page).to have_content("+1 Awesome!")
end
wait_for_requests
end
end
......@@ -21,13 +21,7 @@ Capybara.register_driver :chrome do |app|
options.add_argument("no-sandbox")
# Run headless by default unless CHROME_HEADLESS specified
unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
options.add_argument("headless")
# Chrome documentation says this flag is needed for now
# https://developers.google.com/web/updates/2017/04/headless-chrome#cli
options.add_argument("disable-gpu")
end
options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
......
......@@ -106,9 +106,9 @@ module API
end
def find_project(id)
if id =~ /^\d+$/
if id.is_a?(Integer) || id =~ /^\d+$/
Project.find_by(id: id)
else
elsif id.include?("/")
Project.find_by_full_path(id)
end
end
......
......@@ -12,19 +12,15 @@ module Gitlab
Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
end
def self.org?
Gitlab.config.gitlab.url == 'https://dev.gitlab.org'
end
def self.gl_subdomain?
SUBDOMAIN_REGEX === Gitlab.config.gitlab.url
end
def self.dev_env_or_com?
Rails.env.test? || Rails.env.development? || com?
end
def self.dev?
Gitlab.config.gitlab.url == 'https://dev.gitlab.org'
end
def self.inc_controlled?
dev? || com?
Rails.env.test? || Rails.env.development? || org? || com?
end
end
......@@ -40,7 +40,7 @@ module Gitlab
end
def self.cache_key_for_project(project)
"projects/#{project.id}/pipeline_status"
"#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status"
end
def self.update_for_pipeline(pipeline)
......
......@@ -6,17 +6,22 @@ namespace :cache do
desc "GitLab | Clear redis cache"
task redis: :environment do
Gitlab::Redis::Cache.with do |redis|
cursor = REDIS_SCAN_START_STOP
loop do
cursor, keys = redis.scan(
cursor,
match: "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*",
count: REDIS_CLEAR_BATCH_SIZE
)
cache_key_pattern = %W[#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*
projects/*/pipeline_status]
redis.del(*keys) if keys.any?
cache_key_pattern.each do |match|
cursor = REDIS_SCAN_START_STOP
loop do
cursor, keys = redis.scan(
cursor,
match: match,
count: REDIS_CLEAR_BATCH_SIZE
)
break if cursor == REDIS_SCAN_START_STOP
redis.del(*keys) if keys.any?
break if cursor == REDIS_SCAN_START_STOP
end
end
end
end
......
......@@ -2,10 +2,15 @@ require 'spec_helper'
require 'rainbow/ext/string'
describe 'seed production settings' do
include StubENV
let(:settings_file) { Rails.root.join('db/fixtures/production/010_settings.rb') }
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
before do
# It's important to set this variable so that we don't save a memoized
# (supposed to be) in-memory record in `Gitlab::CurrentSettings.in_memory_application_settings`
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do
before do
stub_env('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN', '013456789')
......
require "spec_helper"
describe "User comments on commit", :js do
include Spec::Support::Helpers::Features::NotesHelpers
include RepoHelpers
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
COMMENT_TEXT = "XML attached".freeze
before do
sign_in(user)
project.add_developer(user)
visit(project_commit_path(project, sample_commit.id))
end
context "when adding new comment" do
it "adds comment" do
EMOJI = ":+1:".freeze
page.within(".js-main-target-form") do
expect(page).not_to have_link("Cancel")
fill_in("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}")
# Check on `Preview` tab
click_link("Preview")
expect(find(".js-md-preview")).to have_content(COMMENT_TEXT).and have_css("gl-emoji")
expect(page).not_to have_css(".js-note-text")
# Check on `Write` tab
click_link("Write")
expect(page).to have_field("note[note]", with: "#{COMMENT_TEXT} #{EMOJI}")
# Submit comment from the `Preview` tab to get rid of a separate `it` block
# which would specially tests if everything gets cleared from the note form.
click_link("Preview")
click_button("Comment")
end
wait_for_requests
page.within(".note") do
expect(page).to have_content(COMMENT_TEXT).and have_css("gl-emoji")
end
page.within(".js-main-target-form") do
expect(page).to have_field("note[note]", with: "").and have_no_css(".js-md-preview")
end
end
end
context "when editing comment" do
before do
add_note(COMMENT_TEXT)
end
it "edits comment" do
NEW_COMMENT_TEXT = "+1 Awesome!".freeze
page.within(".main-notes-list") do
note = find(".note")
note.hover
note.find(".js-note-edit").click
end
page.find(".current-note-edit-form textarea")
page.within(".current-note-edit-form") do
fill_in("note[note]", with: NEW_COMMENT_TEXT)
click_button("Save comment")
end
wait_for_requests
page.within(".note") do
expect(page).to have_content(NEW_COMMENT_TEXT)
end
end
end
context "when deleting comment" do
before do
add_note(COMMENT_TEXT)
end
it "deletes comment" do
page.within(".note") do
expect(page).to have_content(COMMENT_TEXT)
end
page.within(".main-notes-list") do
note = find(".note")
note.hover
find(".more-actions").click
find(".more-actions .dropdown-menu li", match: :first)
accept_confirm { find(".js-note-delete").click }
end
expect(page).not_to have_css(".note")
end
end
end
......@@ -7,11 +7,11 @@ describe 'Projects > Files > User uploads files' do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
let(:project) { create(:project, :repository, name: 'Shop') }
let(:user) { create(:user) }
let(:project) { create(:project, :repository, name: 'Shop', creator: user) }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
let(:user) { project.creator }
before do
project.add_master(user)
......
require 'spec_helper'
describe 'Embedded Snippets' do
let(:snippet) { create(:personal_snippet, :public, file_name: 'random_dir.rb', content: content) }
let(:content) { "require 'fileutils'\nFileUtils.mkdir_p 'some/random_dir'\n" }
it 'loads snippet', :js do
script_url = "http://#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}/#{snippet_path(snippet, format: 'js')}"
embed_body = "<html><body><script src=\"#{script_url}\"></script></body></html>"
rack_app = proc do
['200', { 'Content-Type' => 'text/html' }, [embed_body]]
end
server = Capybara::Server.new(rack_app)
server.boot
visit("http://#{server.host}:#{server.port}/embedded_snippet.html")
expect(page).to have_content("random_dir.rb")
expect(page).to have_content("require 'fileutils'")
expect(page).to have_link('Open raw')
expect(page).to have_link('Download')
end
end
......@@ -162,4 +162,11 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'CHANGELOG')).to eq 'file-text-o'
end
end
describe '#external_snippet_icon' do
it 'returns external snippet icon' do
expect(external_snippet_icon('download').to_s)
.to eq("<span class=\"gl-snippet-icon gl-snippet-icon-download\"></span>")
end
end
end
require 'spec_helper'
describe SnippetsHelper do
include IconsHelper
describe '#embedded_snippet_raw_button' do
it 'gives view raw button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet)}\">#{external_snippet_icon('doc_code')}</a>")
end
it 'gives view raw button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public)
expect(embedded_snippet_raw_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" rel=\"noopener noreferrer\" title=\"Open raw\" href=\"#{raw_snippet_url(@snippet)}\">#{external_snippet_icon('doc_code')}</a>")
end
end
describe '#embedded_snippet_download_button' do
it 'gives download button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_project_snippet_url(@snippet.project, @snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
end
it 'gives download button of embedded snippets for personal snippets' do
@snippet = create(:personal_snippet, :public)
expect(embedded_snippet_download_button.to_s).to eq("<a class=\"btn\" target=\"_blank\" title=\"Download\" rel=\"noopener noreferrer\" href=\"#{raw_snippet_url(@snippet, inline: false)}\">#{external_snippet_icon('download')}</a>")
end
end
end
export default function removeBreakLine (data) {
return data.replace(/\r?\n|\r/g, ' ');
}
/**
* Replaces line break with an empty space
* @param {*} data
*/
export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' ');
/**
* Removes line breaks, spaces and trims the given text
* @param {String} str
* @returns {String}
*/
export const trimText = str =>
str
.replace(/\r?\n|\r/g, '')
.replace(/\s\s+/g, ' ')
.trim();
export const removeWhitespace = str => str.replace(/\s\s+/g, ' ');
......@@ -3,6 +3,7 @@ import store from '~/ide/stores';
import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file } from '../../helpers';
import { removeWhitespace } from '../../../helpers/vue_component_helper';
describe('Multi-file editor commit sidebar list collapsed', () => {
let vm;
......@@ -23,6 +24,6 @@ describe('Multi-file editor commit sidebar list collapsed', () => {
});
it('renders added & modified files count', () => {
expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1');
expect(removeWhitespace(vm.$el.textContent).trim()).toBe('1 1');
});
});
......@@ -27,7 +27,7 @@ describe('RepoLoadingFile', () => {
const lines = [...container.querySelectorAll(':scope > div')];
expect(container).toBeTruthy();
expect(lines.length).toEqual(6);
expect(lines.length).toEqual(3);
assertLines(lines);
});
}
......
This diff is collapsed.
......@@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import pipelinesComp from '~/pipelines/components/pipelines.vue';
import Store from '~/pipelines/stores/pipelines_store';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { pipelineWithStages, stageReply } from './mock_data';
describe('Pipelines', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
......@@ -668,4 +669,79 @@ describe('Pipelines', () => {
});
});
});
describe('updates results when a staged is clicked', () => {
beforeEach(() => {
const copyPipeline = Object.assign({}, pipelineWithStages);
copyPipeline.id += 1;
mock
.onGet('twitter/flight/pipelines.json').reply(200, {
pipelines: [pipelineWithStages],
count: {
all: 1,
finished: 1,
pending: 0,
running: 0,
},
}, {
'POLL-INTERVAL': 100,
})
.onGet(pipelineWithStages.details.stages[0].dropdown_path)
.reply(200, stageReply);
vm = mountComponent(PipelinesComponent, {
store: new Store(),
hasGitlabCi: true,
canCreatePipeline: true,
...paths,
});
});
describe('when a request is being made', () => {
it('stops polling, cancels the request, fetches pipelines & restarts polling', (done) => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
spyOn(vm.service.cancelationSource, 'cancel').and.callThrough();
setTimeout(() => {
vm.isMakingRequest = true;
return vm.$nextTick()
.then(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
})
.then(() => {
expect(vm.service.cancelationSource.cancel).toHaveBeenCalled();
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
});
}, 0);
});
});
describe('when no request is being made', () => {
it('stops polling, fetches pipelines & restarts polling', (done) => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
setTimeout(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
}, 0);
});
});
});
});
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import stage from '~/pipelines/components/stage.vue';
import eventHub from '~/pipelines/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines stage component', () => {
......@@ -43,13 +44,15 @@ describe('Pipelines stage component', () => {
mock.onGet('path.json').reply(200, { html: 'foo' });
});
it('should render the received data', done => {
it('should render the received data and emit `clickedDropdown` event', done => {
spyOn(eventHub, '$emit');
component.$el.querySelector('button').click();
setTimeout(() => {
expect(
component.$el.querySelector('.js-builds-dropdown-container ul').textContent.trim(),
).toEqual('foo');
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
done();
}, 0);
});
......
import findAndFollowLink from '~/shortcuts_dashboard_navigation';
import * as urlUtility from '~/lib/utils/url_utility';
describe('findAndFollowLink', () => {
it('visits a link when the selector exists', () => {
const href = '/some/path';
const locationSpy = spyOn(urlUtility, 'visitUrl');
setFixtures(`<a class="my-shortcut" href="${href}">link</a>`);
findAndFollowLink('.my-shortcut');
expect(locationSpy).toHaveBeenCalledWith(href);
});
it('does not throw an exception when the selector does not exist', () => {
const locationSpy = spyOn(urlUtility, 'visitUrl');
// this should not throw an exception
findAndFollowLink('.this-selector-does-not-exist');
expect(locationSpy).not.toHaveBeenCalled();
});
});
import Vue from 'vue';
import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import removeBreakLine from 'spec/helpers/vue_component_helper';
import { removeBreakLine } from 'spec/helpers/vue_component_helper';
describe('MRWidgetConflicts', () => {
let Component;
......
import Vue from 'vue';
import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import removeBreakLine from 'spec/helpers/vue_component_helper';
import { removeBreakLine } from 'spec/helpers/vue_component_helper';
describe('MRWidgetPipelineBlocked', () => {
let vm;
......
import Vue from 'vue';
import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import removeBreakLine from 'spec/helpers/vue_component_helper';
import { removeBreakLine } from 'spec/helpers/vue_component_helper';
describe('ShaMismatch', () => {
let vm;
......
......@@ -14,8 +14,8 @@ describe('Skeleton loading container', () => {
vm.$destroy();
});
it('renders 6 skeleton lines by default', () => {
expect(vm.$el.querySelector('.skeleton-line-6')).not.toBeNull();
it('renders 3 skeleton lines by default', () => {
expect(vm.$el.querySelector('.skeleton-line-3')).not.toBeNull();
});
it('renders in full mode by default', () => {
......
......@@ -3,6 +3,48 @@ require 'spec_helper'
describe API::Helpers do
subject { Class.new.include(described_class).new }
describe '#find_project' do
let(:project) { create(:project) }
shared_examples 'project finder' do
context 'when project exists' do
it 'returns requested project' do
expect(subject.find_project(existing_id)).to eq(project)
end
it 'returns nil' do
expect(subject.find_project(non_existing_id)).to be_nil
end
end
end
context 'when ID is used as an argument' do
let(:existing_id) { project.id }
let(:non_existing_id) { (Project.maximum(:id) || 0) + 1 }
it_behaves_like 'project finder'
end
context 'when PATH is used as an argument' do
let(:existing_id) { project.full_path }
let(:non_existing_id) { 'something/else' }
it_behaves_like 'project finder'
context 'with an invalid PATH' do
let(:non_existing_id) { 'undefined' } # path without slash
it_behaves_like 'project finder'
it 'does not hit the database' do
expect(Project).not_to receive(:find_by_full_path)
subject.find_project(non_existing_id)
end
end
end
end
describe '#find_namespace' do
let(:namespace) { create(:namespace) }
......
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
let!(:project) { create(:project, :repository) }
let(:pipeline_status) { described_class.new(project) }
let(:cache_key) { "projects/#{project.id}/pipeline_status" }
let(:cache_key) { described_class.cache_key_for_project(project) }
describe '.load_for_project' do
it "loads the status" do
......
......@@ -16,6 +16,8 @@ describe Gitlab do
it 'is true when on other gitlab subdomain' do
stub_config_setting(url: 'https://example.gitlab.com')
expect(described_class.com?).to eq true
end
it 'is false when not on GitLab.com' do
......
......@@ -5,7 +5,7 @@ describe InternalId do
let(:usage) { :issues }
let(:issue) { build(:issue, project: project) }
let(:scope) { { project: project } }
let(:init) { ->(s) { s.project.issues.size } }
let(:init) { ->(s) { s.project.issues.maximum(:iid) } }
context 'validations' do
it { is_expected.to validate_presence_of(:usage) }
......@@ -39,6 +39,29 @@ describe InternalId do
end
end
context 'with an InternalId record present and existing issues with a higher internal id' do
# This can happen if the old NonatomicInternalId is still in use
before do
issues = Array.new(rand(1..10)).map { create(:issue, project: project) }
issue = issues.last
issue.iid = issues.map { |i| i.iid }.max + 1
issue.save
end
let(:maximum_iid) { project.issues.map { |i| i.iid }.max }
it 'updates last_value to the maximum internal id present' do
subject
expect(described_class.find_by(project: project, usage: described_class.usages[usage.to_s]).last_value).to eq(maximum_iid + 1)
end
it 'returns next internal id correctly' do
expect(subject).to eq(maximum_iid + 1)
end
end
context 'with concurrent inserts on table' do
it 'looks up the record if it was created concurrently' do
args = { **scope, usage: described_class.usages[usage.to_s] }
......@@ -81,7 +104,8 @@ describe InternalId do
describe '#increment_and_save!' do
let(:id) { create(:internal_id) }
subject { id.increment_and_save! }
let(:maximum_iid) { nil }
subject { id.increment_and_save!(maximum_iid) }
it 'returns incremented iid' do
value = id.last_value
......@@ -102,5 +126,14 @@ describe InternalId do
expect(subject).to eq(1)
end
end
context 'with maximum_iid given' do
let(:id) { create(:internal_id, last_value: 1) }
let(:maximum_iid) { id.last_value + 10 }
it 'returns maximum_iid instead' do
expect(subject).to eq(12)
end
end
end
end
......@@ -7,6 +7,16 @@ require 'selenium-webdriver'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
# Define an error class for JS console messages
JSConsoleError = Class.new(StandardError)
# Filter out innocuous JS console messages
JS_CONSOLE_FILTER = Regexp.union([
'"[HMR] Waiting for update signal from WDS..."',
'"[WDS] Hot Module Replacement enabled."',
"Download the Vue Devtools extension"
])
Capybara.register_driver :chrome do |app|
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
# This enables access to logs with `page.driver.manage.get_log(:browser)`
......@@ -25,13 +35,7 @@ Capybara.register_driver :chrome do |app|
options.add_argument("no-sandbox")
# Run headless by default unless CHROME_HEADLESS specified
unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
options.add_argument("headless")
# Chrome documentation says this flag is needed for now
# https://developers.google.com/web/updates/2017/04/headless-chrome#cli
options.add_argument("disable-gpu")
end
options.add_argument("headless") unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
options.add_argument("disable-dev-shm-usage") if ENV['CI'] || ENV['CI_SERVER']
......@@ -78,6 +82,15 @@ RSpec.configure do |config|
end
config.after(:example, :js) do |example|
# when a test fails, display any messages in the browser's console
if example.exception
console = page.driver.browser.manage.logs.get(:browser)&.reject { |log| log.message =~ JS_CONSOLE_FILTER }
if console.present?
message = "Unexpected browser console output:\n" + console.map(&:message).join("\n")
raise JSConsoleError, message
end
end
# prevent localStorage from introducing side effects based on test order
unless ['', 'about:blank', 'data:,'].include? Capybara.current_session.driver.browser.current_url
execute_script("localStorage.clear();")
......
require 'rake_helper'
describe 'clearing redis cache' do
before do
Rake.application.rake_require 'tasks/cache'
end
describe 'clearing pipeline status cache' do
let(:pipeline_status) { create(:ci_pipeline).project.pipeline_status }
before do
allow(pipeline_status).to receive(:loaded).and_return(nil)
end
it 'clears pipeline status cache' do
expect { run_rake_task('cache:clear:redis') }.to change { pipeline_status.has_cache? }
end
end
end
......@@ -18,4 +18,10 @@ describe 'admin/dashboard/index.html.haml' do
expect(rendered).to have_content 'GitLab Workhorse'
expect(rendered).to have_content Gitlab::Workhorse.version
end
it "includes revision of GitLab" do
render
expect(rendered).to have_content "#{Gitlab::VERSION} (#{Gitlab::REVISION})"
end
end
......@@ -315,6 +315,7 @@ production:
mv clair-scanner_linux_amd64 clair-scanner
chmod +x clair-scanner
touch clair-whitelist.yml
while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
}
......
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