Commit e1bd9140 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'ee/master' into 41731-predicate-memoization-ee

* ee/master: (227 commits)
  Docs: move article "Autoscaling Runners on AWS" to its topic-related dir
  Don't run Geo specs in non-Geo jobs
  Gracefully handle case when no shard data is available
  Replace CI with CI/CD
  Update clair-scanner to v8
  Refactor Docker SAST docs
  Rename SAST Docker docs
  Add user docs for sast:image feature
  Add sast:image section to autodevops docs
  Update documentation for sast:image feature
  Add sast image documentation to CI examples
  Docs: remove duplicate CI examples
  Fix Geo::RenameRepositoryService spec when repo is already on disk
  remove app/assets/javascripts/dispatcher.js.rej
  Add EE specific spec fixes
  Generalize protected branch/tag dropdowns
  Add chaneglog entry
  Add test for neutralCount
  Fix neutral count computation
  Merge branch 'mk-fix-api-geo-spec' into 'master'
  ...
parents 12defab0 b3a3ae67
......@@ -8,7 +8,8 @@
"plugins": [
["istanbul", {
"exclude": [
"spec/javascripts/**/*"
"spec/javascripts/**/*",
"app/assets/javascripts/locale/**/app.js"
]
}],
["transform-define", {
......
......@@ -117,7 +117,7 @@ stages:
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
- scripts/gitaly-test-spawn
- knapsack rspec "--color --format documentation"
- knapsack rspec "--color --format documentation --tag ~geo"
artifacts:
expire_in: 31d
when: always
......@@ -629,6 +629,7 @@ codequality:
paths: [codeclimate.json]
sast:
<<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest
before_script: []
script:
......
app/assets/images/multi-editor-on.png

5.34 KB | W: | H:

app/assets/images/multi-editor-on.png

3.88 KB | W: | H:

app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
app/assets/images/multi-editor-on.png
  • 2-up
  • Swipe
  • Onion skin
import _ from 'underscore';
export default class ProtectedTagDropdown {
export default class CreateItemDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
......@@ -8,11 +8,14 @@ export default class ProtectedTagDropdown {
* $dropdown must be an element created using `dropdown_tag()` rails helper
*/
constructor(options) {
this.onSelect = options.onSelect;
this.defaultToggleLabel = options.defaultToggleLabel;
this.fieldName = options.fieldName;
this.onSelect = options.onSelect || (() => {});
this.getDataOption = options.getData;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedTag = this.$dropdownContainer.find('.js-create-new-protected-tag');
this.$createButton = this.$dropdownContainer.find('.js-dropdown-create-new-item');
this.buildDropdown();
this.bindEvents();
......@@ -23,7 +26,7 @@ export default class ProtectedTagDropdown {
buildDropdown() {
this.$dropdown.glDropdown({
data: this.getProtectedTags.bind(this),
data: this.getData.bind(this),
filterable: true,
remote: false,
search: {
......@@ -31,14 +34,14 @@ export default class ProtectedTagDropdown {
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Tag';
return (selected && 'id' in selected) ? selected.title : this.defaultToggleLabel;
},
fieldName: 'protected_tag[name]',
text(protectedTag) {
return _.escape(protectedTag.title);
fieldName: this.fieldName,
text(item) {
return _.escape(item.title);
},
id(protectedTag) {
return _.escape(protectedTag.id);
id(item) {
return _.escape(item.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
......@@ -49,37 +52,37 @@ export default class ProtectedTagDropdown {
}
bindEvents() {
this.$protectedTag.on('click', this.onClickCreateWildcard.bind(this));
this.$createButton.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard(e) {
e.preventDefault();
// Refresh the dropdown's data, which ends up calling `getData`
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
e.preventDefault();
}
getProtectedTags(term, callback) {
if (this.selectedTag) {
callback(gon.open_tags.concat(this.selectedTag));
} else {
callback(gon.open_tags);
}
getData(term, callback) {
this.getDataOption(term, (data = []) => {
callback(data.concat(this.selectedItem || []));
});
}
toggleCreateNewButton(tagName) {
if (tagName) {
this.selectedTag = {
title: tagName,
id: tagName,
text: tagName,
toggleCreateNewButton(item) {
if (item) {
this.selectedItem = {
title: item,
id: item,
text: item,
};
this.$dropdownContainer
.find('.js-create-new-protected-tag code')
.text(tagName);
.find('.js-dropdown-create-new-item code')
.text(item);
}
this.toggleFooter(!tagName);
this.toggleFooter(!item);
}
toggleFooter(toggleState) {
......
......@@ -43,8 +43,7 @@
</h5>
<a
:href="mergeRequest.url"
class="issue-link"
>
class="issue-link">
!{{ mergeRequest.iid }}
</a>
&middot;
......@@ -52,8 +51,7 @@
{{ s__('OpenedNDaysAgo|Opened') }}
<a
:href="mergeRequest.url"
class="issue-date"
>
class="issue-date">
{{ mergeRequest.createdAt }}
</a>
</span>
......@@ -61,8 +59,7 @@
{{ s__('ByAuthor|by') }}
<a
:href="mergeRequest.author.webUrl"
class="issue-author-link"
>
class="issue-author-link">
{{ mergeRequest.author.name }}
</a>
</span>
......
......@@ -47,18 +47,14 @@
<a
:href="issue.url"
class="issue-link"
>
#{{ issue.iid }}
</a>
>#{{ issue.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
<a
:href="issue.url"
class="issue-date"
>
{{ issue.createdAt }}
</a>
>{{ issue.createdAt }}</a>
</span>
<span>
{{ s__('ByAuthor|by') }}
......
......@@ -57,9 +57,7 @@
<a
:href="commit.commitUrl"
class="commit-hash-link commit-sha"
>
{{ commit.shortSha }}
</a>
>{{ commit.shortSha }}</a>
{{ s__('FirstPushedBy|pushed by') }}
<a
:href="commit.author.webUrl"
......
......@@ -46,9 +46,7 @@
<a
:href="mergeRequest.url"
class="issue-link"
>
!{{ mergeRequest.iid }}
</a>
>!{{ mergeRequest.iid }}</a>
&middot;
<span>
{{ s__('OpenedNDaysAgo|Opened') }}
......
......@@ -63,7 +63,8 @@
</a>
<span
class="icon-branch"
v-html="iconBranch">
v-html="iconBranch"
>
</span>
<a
:href="build.commitUrl"
......
......@@ -80,16 +80,14 @@
</span>
<a
:href="build.commitUrl"
class="commit-sha"
>
class="commit-sha">
{{ build.shortSha }}
</a>
</h5>
<span>
<a
:href="build.url"
class="issue-date"
>
class="issue-date">
{{ build.date }}
</a>
</span>
......
diff a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js (rejected hunks)
@@ -12,7 +12,6 @@ import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter';
-import groupsSelect from './groups_select';
import Search from './search';
import initAdmin from './admin';
import NamespaceSelect from './namespace_select';
......@@ -12,7 +12,6 @@
components: {
loadingIcon,
},
props: {
actions: {
type: Array,
......@@ -69,7 +68,8 @@
<span v-html="playIconSvg"></span>
<i
class="fa fa-caret-down"
aria-hidden="true">
aria-hidden="true"
>
</i>
<loading-icon v-if="isLoading" />
</span>
......@@ -78,8 +78,7 @@
<ul class="dropdown-menu dropdown-menu-align-right">
<li
v-for="(action, i) in actions"
:key="i"
>
:key="i">
<button
type="button"
class="js-manual-action-link no-btn btn"
......
......@@ -3,13 +3,12 @@
import { s__ } from '../../locale';
/**
* Renders the external url link in environments table.
*/
* Renders the external url link in environments table.
*/
export default {
directives: {
tooltip,
},
props: {
externalUrl: {
type: String,
......
<script>
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
* Renders the Monitoring (Metrics) link in environments table.
*/
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
props: {
monitoringUrl: {
type: String,
......
<script>
/**
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
......@@ -12,6 +12,7 @@
components: {
loadingIcon,
},
props: {
retryUrl: {
type: String,
......
<script>
/**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
......@@ -11,6 +11,7 @@
components: {
loadingIcon,
},
directives: {
tooltip,
},
......
<script>
/**
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
import terminalIconSvg from 'icons/_icon_terminal.svg';
import tooltip from '../../vue_shared/directives/tooltip';
......@@ -10,6 +10,7 @@
directives: {
tooltip,
},
props: {
terminalPath: {
type: String,
......@@ -17,6 +18,7 @@
default: '',
},
},
data() {
return {
terminalIconSvg,
......
......@@ -32,7 +32,6 @@ export default {
default: false,
},
},
methods: {
folderUrl(model) {
return `${window.location.pathname}/folders/${model.folderName}`;
......@@ -88,8 +87,7 @@ export default {
</div>
<template
v-for="(model, i) in environments"
:model="model"
>
:model="model">
<div
is="environment-item"
:model="model"
......@@ -117,8 +115,7 @@ export default {
>
<div
v-if="model.isLoadingFolderContent"
:key="i"
>
:key="i">
<loading-icon size="2" />
</div>
......
......@@ -29,7 +29,6 @@
required: true,
},
},
methods: {
successCallback(resp) {
this.saveData(resp);
......
<script>
/* global Flash */
import { s__ } from '~/locale';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import { getParameterByName } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import { getParameterByName } from '../../lib/utils/common_utils';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import { COMMON_STR } from '../constants';
import { mergeUrlParams } from '../../lib/utils/url_utility';
import groupsComponent from './groups.vue';
export default {
components: {
loadingIcon,
modal,
groupsComponent,
},
props: {
......@@ -32,6 +36,10 @@ export default {
isLoading: true,
isSearchEmpty: false,
searchEmptyMessage: '',
showModal: false,
groupLeaveConfirmationMessage: '',
targetGroup: null,
targetParentGroup: null,
};
},
computed: {
......@@ -48,7 +56,7 @@ export default {
eventHub.$on('fetchPage', this.fetchPage);
eventHub.$on('toggleChildren', this.toggleChildren);
eventHub.$on('leaveGroup', this.leaveGroup);
eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
eventHub.$on('updatePagination', this.updatePagination);
eventHub.$on('updateGroups', this.updateGroups);
},
......@@ -58,7 +66,7 @@ export default {
beforeDestroy() {
eventHub.$off('fetchPage', this.fetchPage);
eventHub.$off('toggleChildren', this.toggleChildren);
eventHub.$off('leaveGroup', this.leaveGroup);
eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
eventHub.$off('updatePagination', this.updatePagination);
eventHub.$off('updateGroups', this.updateGroups);
},
......@@ -141,14 +149,23 @@ export default {
parentGroup.isOpen = false;
}
},
leaveGroup(group, parentGroup) {
const targetGroup = group;
targetGroup.isBeingRemoved = true;
this.service.leaveGroup(targetGroup.leavePath)
showLeaveGroupModal(group, parentGroup) {
this.targetGroup = group;
this.targetParentGroup = parentGroup;
this.showModal = true;
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
},
hideLeaveGroupModal() {
this.showModal = false;
},
leaveGroup() {
this.showModal = false;
this.targetGroup.isBeingRemoved = true;
this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json())
.then((res) => {
$.scrollTo(0);
this.store.removeGroup(targetGroup, parentGroup);
this.store.removeGroup(this.targetGroup, this.targetParentGroup);
Flash(res.notice, 'notice');
})
.catch((err) => {
......@@ -157,7 +174,7 @@ export default {
message = COMMON_STR.LEAVE_FORBIDDEN;
}
Flash(message);
targetGroup.isBeingRemoved = false;
this.targetGroup.isBeingRemoved = false;
});
},
updatePagination(headers) {
......@@ -190,5 +207,14 @@ export default {
:search-empty-message="searchEmptyMessage"
:page-info="pageInfo"
/>
<modal
v-show="showModal"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
:text="groupLeaveConfirmationMessage"
@cancel="hideLeaveGroupModal"
@submit="leaveGroup"
/>
</div>
</template>
<script>
import { s__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import modal from '~/vue_shared/components/modal.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
import { COMMON_STR } from '../constants';
export default {
components: {
icon,
modal,
export default {
components: {
icon,
},
directives: {
tooltip,
},
props: {
parentGroup: {
type: Object,
required: false,
default: () => ({}),
},
directives: {
tooltip,
group: {
type: Object,
required: true,
},
props: {
parentGroup: {
type: Object,
required: false,
default: () => ({}),
},
group: {
type: Object,
required: true,
},
},
computed: {
leaveBtnTitle() {
return COMMON_STR.LEAVE_BTN_TITLE;
},
data() {
return {
modalStatus: false,
};
editBtnTitle() {
return COMMON_STR.EDIT_BTN_TITLE;
},
computed: {
leaveBtnTitle() {
return COMMON_STR.LEAVE_BTN_TITLE;
},
editBtnTitle() {
return COMMON_STR.EDIT_BTN_TITLE;
},
leaveConfirmationMessage() {
return s__(`GroupsTree|Are you sure you want to leave the "${this.group.fullName}" group?`);
},
},
methods: {
onLeaveGroup() {
eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
},
methods: {
onLeaveGroup() {
this.modalStatus = true;
},
leaveGroup() {
this.modalStatus = false;
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
},
},
};
},
};
</script>
<template>
......@@ -78,14 +63,5 @@
class="leave-group btn no-expand">
<icon name="leave"/>
</a>
<modal
v-show="modalStatus"
:primary-button-label="__('Leave')"
kind="warning"
:title="__('Are you sure?')"
:text="__('Are you sure you want to leave this group?')"
:body="leaveConfirmationMessage"
@submit="leaveGroup"
/>
</div>
</template>
......@@ -41,7 +41,7 @@ export default {
</div>
</div>
<div>
<repo-tree :tree-id="branch.treeId"/>
<repo-tree :tree-id="branch.treeId" />
</div>
</div>
</template>
......@@ -73,7 +73,8 @@
<div
class="multi-file-loading-container"
v-for="n in 3"
:key="n">
:key="n"
>
<skeleton-loading-container />
</div>
</template>
......
......@@ -61,11 +61,10 @@
v-else
class="vertical-center render-error">
<p class="text-center">
The source could not be displayed because a rendering error occurred. You can
<a
The source could not be displayed because a rendering error occurred.
You can <a
:href="activeFile.rawPath"
download
>download</a> it instead.
download>download</a> it instead.
</p>
</div>
</div>
......
......@@ -267,6 +267,7 @@
:project-path="projectPath"
:project-namespace="projectNamespace"
:show-delete-button="showDeleteButton"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/>
......
......@@ -66,7 +66,6 @@
window.addEventListener('resize', this.resizeThrottled, false);
}
},
methods: {
getGraphsData() {
this.state = 'loading';
......
......@@ -259,7 +259,8 @@
<svg
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
ref="graphData"
>
<graph-path
v-for="(path, index) in timeSeries"
:key="index"
......
<script>
import { dateFormat, timeFormat } from '../../utils/date_time_formatters';
import { formatRelevantDigits } from '../../../lib/utils/number_utils';
import Icon from '../../../vue_shared/components/icon.vue';
import icon from '../../../vue_shared/components/icon.vue';
export default {
components: {
Icon,
icon,
},
props: {
currentXCoordinate: {
......
......@@ -51,6 +51,7 @@
return `note_${this.note.id}`;
},
},
created() {
eventHub.$on('enterEditMode', ({ noteId }) => {
if (noteId === this.note.id) {
......@@ -59,6 +60,7 @@
}
});
},
methods: {
...mapActions([
'deleteNote',
......
......@@ -14,6 +14,7 @@
directives: {
tooltip,
},
props: {
tooltipText: {
type: String,
......
......@@ -63,7 +63,8 @@
* target the click event of this component.
*/
stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item'))
$(this.$el
.querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item'))
.on('click', (e) => {
e.stopPropagation();
});
......
......@@ -250,7 +250,8 @@
<div
class="blank-state-row"
v-if="shouldRenderNoPipelinesMessage">
v-if="shouldRenderNoPipelinesMessage"
>
<div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div>
......
......@@ -162,7 +162,8 @@
<ul
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
aria-labelledby="stageDropdown">
aria-labelledby="stageDropdown"
>
<li
:class="dropdownClass"
......
import _ from 'underscore';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import ProtectedBranchDropdown from './protected_branch_dropdown';
import CreateItemDropdown from '../create_item_dropdown';
import AccessorUtilities from '../lib/utils/accessor';
const PB_LOCAL_STORAGE_KEY = 'protected-branches-defaults';
......@@ -35,10 +35,12 @@ export default class ProtectedBranchCreate {
onSelect: this.onSelectCallback,
});
// Protected branch dropdown
this.protectedBranchDropdown = new ProtectedBranchDropdown({
this.createItemDropdown = new CreateItemDropdown({
$dropdown: $protectedBranchDropdown,
defaultToggleLabel: 'Protected Branch',
fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback,
getData: ProtectedBranchCreate.getProtectedBranches,
});
this.loadPreviousSelection($allowedToMergeDropdown.data('glDropdown'), $allowedToPushDropdown.data('glDropdown'));
......@@ -60,6 +62,10 @@ export default class ProtectedBranchCreate {
this.$form.find('input[type="submit"]').attr('disabled', completedForm);
}
static getProtectedBranches(term, callback) {
callback(gon.open_branches);
}
loadPreviousSelection(mergeDropdown, pushDropdown) {
let mergeIndex = 0;
let pushIndex = 0;
......
import _ from 'underscore';
export default class ProtectedBranchDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
* `onSelect` event callback
* $dropdown must be an element created using `dropdown_branch()` rails helper
*/
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedBranch = this.$dropdownContainer.find('.js-create-new-protected-branch');
this.buildDropdown();
this.bindEvents();
// Hide footer
this.toggleFooter(true);
}
buildDropdown() {
this.$dropdown.glDropdown({
data: this.getProtectedBranches.bind(this),
filterable: true,
remote: false,
search: {
fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
},
fieldName: 'protected_branch[name]',
text(protectedBranch) {
return _.escape(protectedBranch.title);
},
id(protectedBranch) {
return _.escape(protectedBranch.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
options.e.preventDefault();
this.onSelect();
},
});
}
bindEvents() {
this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard(e) {
e.preventDefault();
// Refresh the dropdown's data, which ends up calling `getProtectedBranches`
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
}
getProtectedBranches(term, callback) {
if (this.selectedBranch) {
callback(gon.open_branches.concat(this.selectedBranch));
} else {
callback(gon.open_branches);
}
}
toggleCreateNewButton(branchName) {
if (branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName,
};
this.$dropdownContainer
.find('.js-create-new-protected-branch code')
.text(branchName);
}
this.toggleFooter(!branchName);
}
toggleFooter(toggleState) {
this.$dropdownFooter.toggleClass('hidden', toggleState);
}
}
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
import ProtectedTagDropdown from './protected_tag_dropdown';
import CreateItemDropdown from '../create_item_dropdown';
export default class ProtectedTagCreate {
constructor() {
......@@ -24,9 +24,12 @@ export default class ProtectedTagCreate {
$allowedToCreateDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected tag dropdown
this.protectedTagDropdown = new ProtectedTagDropdown({
this.createItemDropdown = new CreateItemDropdown({
$dropdown: this.$form.find('.js-protected-tag-select'),
defaultToggleLabel: 'Protected Tag',
fieldName: 'protected_tag[name]',
onSelect: this.onSelectCallback,
getData: ProtectedTagCreate.getProtectedTags,
});
}
......@@ -38,4 +41,8 @@ export default class ProtectedTagCreate {
this.$form.find('input[type="submit"]').attr('disabled', !($tagInput.val() && $allowedToCreateInput.length));
}
static getProtectedTags(term, callback) {
callback(gon.open_tags);
}
}
import _ from 'underscore';
export default class ProtectedTagDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
* `onSelect` event callback
* $dropdown must be an element created using `dropdown_tag()` rails helper
*/
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedTag = this.$dropdownContainer.find('.js-create-new-protected-tag');
this.buildDropdown();
this.bindEvents();
// Hide footer
this.toggleFooter(true);
}
buildDropdown() {
this.$dropdown.glDropdown({
data: this.getProtectedTags.bind(this),
filterable: true,
remote: false,
search: {
fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Tag';
},
fieldName: 'protected_tag[name]',
text(protectedTag) {
return _.escape(protectedTag.title);
},
id(protectedTag) {
return _.escape(protectedTag.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
options.e.preventDefault();
this.onSelect();
},
});
}
bindEvents() {
this.$protectedTag.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard(e) {
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
e.preventDefault();
}
getProtectedTags(term, callback) {
if (this.selectedTag) {
callback(gon.open_tags.concat(this.selectedTag));
} else {
callback(gon.open_tags);
}
}
toggleCreateNewButton(tagName) {
if (tagName) {
this.selectedTag = {
title: tagName,
id: tagName,
text: tagName,
};
this.$dropdownContainer
.find('.js-create-new-protected-tag code')
.text(tagName);
}
this.toggleFooter(!tagName);
}
toggleFooter(toggleState) {
this.$dropdownFooter.toggleClass('hidden', toggleState);
}
}
......@@ -96,7 +96,8 @@
<span
v-tooltip
:title="item.revision"
data-placement="bottom">
data-placement="bottom"
>
{{ item.shortRevision }}
</span>
</td>
......@@ -121,10 +122,12 @@
:aria-label="s__('ContainerRegistry|Remove tag')"
data-container="body"
v-tooltip
@click="handleDeleteRegistry(item)">
@click="handleDeleteRegistry(item)"
>
<i
class="fa fa-trash"
aria-hidden="true">
aria-hidden="true"
>
</i>
</button>
</td>
......
......@@ -101,7 +101,8 @@
<div
v-for="participant in visibleParticipants"
:key="participant.id"
class="participants-author js-participants-author">
class="participants-author js-participants-author"
>
<a
class="author_link"
:href="participant.web_url"
......
......@@ -28,7 +28,6 @@ export default {
beforeDestroy() {
eventHub.$off('toggleSubscription', this.onToggleSubscription);
},
methods: {
onToggleSubscription() {
this.mediator.toggleSubscription()
......
......@@ -126,7 +126,6 @@
<template v-if="pipeline.coverage">
Coverage {{ pipeline.coverage }}%
</template>
</div>
</template>
</div>
......
......@@ -127,7 +127,8 @@
v-if="action.type === 'link'"
:href="action.path"
:class="action.cssClass"
:key="i">
:key="i"
>
{{ action.label }}
</a>
......
......@@ -121,7 +121,8 @@
/>
<div
class="md-write-holder"
v-show="!previewMarkdown">
v-show="!previewMarkdown"
>
<div class="zen-backdrop">
<slot name="textarea"></slot>
<a
......
......@@ -71,13 +71,15 @@
class="js-preview-link"
href="#md-preview-holder"
tabindex="-1"
@click.prevent="previewMarkdownTab($event)">
@click.prevent="previewMarkdownTab($event)"
>
Preview
</a>
</li>
<li
class="md-header-toolbar"
:class="{ active: !previewMarkdown }">
:class="{ active: !previewMarkdown }"
>
<toolbar-button
tag="**"
button-title="Add bold text"
......@@ -125,7 +127,8 @@
data-container="body"
tabindex="-1"
title="Go full screen"
type="button">
type="button"
>
<icon
name="screen-full"
/>
......
......@@ -16,7 +16,6 @@
return this.quickActionsDocsPath !== '';
},
},
};
</script>
......@@ -27,7 +26,8 @@
<a
:href="markdownDocsPath"
target="_blank"
tabindex="-1">
tabindex="-1"
>
Markdown is supported
</a>
</template>
......
<script>
/* eslint-disable vue/require-default-prop */
/* eslint-disable vue/require-default-prop */
export default {
name: 'Modal',
......
<script>
import modal from './modal.vue';
import modal from './modal.vue';
export default {
name: 'RecaptchaModal',
export default {
name: 'RecaptchaModal',
components: {
modal,
},
components: {
modal,
},
props: {
html: {
type: String,
required: false,
default: '',
props: {
html: {
type: String,
required: false,
default: '',
},
},
},
data() {
return {
script: {},
scriptSrc: 'https://www.google.com/recaptcha/api.js',
};
},
data() {
return {
script: {},
scriptSrc: 'https://www.google.com/recaptcha/api.js',
};
},
watch: {
html() {
this.appendRecaptchaScript();
watch: {
html() {
this.appendRecaptchaScript();
},
},
},
mounted() {
window.recaptchaDialogCallback = this.submit.bind(this);
},
mounted() {
window.recaptchaDialogCallback = this.submit.bind(this);
},
methods: {
appendRecaptchaScript() {
this.removeRecaptchaScript();
methods: {
appendRecaptchaScript() {
this.removeRecaptchaScript();
const script = document.createElement('script');
script.src = this.scriptSrc;
script.classList.add('js-recaptcha-script');
script.async = true;
script.defer = true;
const script = document.createElement('script');
script.src = this.scriptSrc;
script.classList.add('js-recaptcha-script');
script.async = true;
script.defer = true;
this.script = script;
this.script = script;
document.body.appendChild(script);
},
document.body.appendChild(script);
},
removeRecaptchaScript() {
if (this.script instanceof Element) this.script.remove();
},
removeRecaptchaScript() {
if (this.script instanceof Element) this.script.remove();
},
close() {
this.removeRecaptchaScript();
this.$emit('close');
},
close() {
this.removeRecaptchaScript();
this.$emit('close');
},
submit() {
this.$el.querySelector('form').submit();
submit() {
this.$el.querySelector('form').submit();
},
},
},
};
};
</script>
<template>
......
......@@ -37,6 +37,9 @@ export default {
},
},
computed: {
neutralCount() {
return this.totalCount - this.successCount - this.failureCount;
},
successPercent() {
return this.getPercent(this.successCount);
},
......@@ -56,14 +59,13 @@ export default {
return this.getTooltip(this.failureLabel, this.failureCount);
},
neutralPercent() {
return 100 - this.successPercent - this.failurePercent;
return this.getPercent(this.neutralCount);
},
neutralBarStyle() {
return this.barStyle(this.neutralPercent);
},
neutralTooltip() {
const neutralCount = this.totalCount - this.successCount - this.failureCount;
return this.getTooltip(this.neutralLabel, neutralCount);
return this.getTooltip(this.neutralLabel, this.neutralCount);
},
},
methods: {
......
......@@ -666,6 +666,16 @@
}
}
.dropdown-create-new-item-button {
@include dropdown-link;
width: 100%;
background-color: transparent;
border: 0;
text-align: left;
text-overflow: ellipsis;
}
.dropdown-loading {
position: absolute;
top: 0;
......
......@@ -18,14 +18,9 @@
margin: $gl-padding 0;
&.limited-width-container .file-content {
max-width: $limited-layout-width-sm;
max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
@media (min-width: $screen-md-min) {
padding-top: 64px;
padding-bottom: 64px;
}
}
}
......@@ -128,7 +123,7 @@
}
&.wiki {
padding: 30px $gl-padding;
padding: $gl-padding;
}
&.blob-no-preview {
......
......@@ -914,17 +914,6 @@ a.allowed-to-push {
}
}
.create-new-protected-branch-button,
.create-new-protected-tag-button {
@include dropdown-link;
width: 100%;
background-color: transparent;
border: 0;
text-align: left;
text-overflow: ellipsis;
}
.protected-branches-list,
.protected-tags-list {
margin-bottom: 30px;
......
......@@ -16,12 +16,6 @@
display: inline-block;
}
@media (min-width: $screen-md-min) {
.blob-viewer[data-type="rich"] {
margin: 20px;
}
}
.ide-view {
display: flex;
height: calc(100vh - #{$header-height});
......
class Projects::Clusters::GcpController < Projects::ApplicationController
before_action :authorize_read_cluster!
before_action :authorize_google_api, except: [:login]
before_action :authorize_google_project_billing, only: [:new]
before_action :authorize_google_project_billing, only: [:new, :create]
before_action :authorize_create_cluster!, only: [:new, :create]
before_action :verify_billing, only: [:create]
def login
begin
......@@ -23,24 +24,34 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
end
def create
@cluster = ::Clusters::CreateService
.new(project, current_user, create_params)
.execute(token_in_session)
if @cluster.persisted?
redirect_to project_cluster_path(project, @cluster)
else
render :new
end
end
private
def verify_billing
case google_project_billing_status
when 'true'
@cluster = ::Clusters::CreateService
.new(project, current_user, create_params)
.execute(token_in_session)
return redirect_to project_cluster_path(project, @cluster) if @cluster.persisted?
return
when 'false'
flash[:error] = _('Please enable billing for one of your projects to be able to create a cluster.')
flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
else
flash[:error] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
end
@cluster = ::Clusters::Cluster.new(create_params)
render :new
end
private
def create_params
params.require(:cluster).permit(
:enabled,
......
......@@ -46,7 +46,7 @@ module BlobHelper
end
def ide_edit_text
"#{_('Multi Edit')} <span class='label label-primary'>#{_('Beta')}</span>".html_safe
"#{_('Web IDE')}"
end
def ide_blob_link(project = @project, ref = @ref, path = @path, options = {})
......
......@@ -49,6 +49,7 @@ class MergeRequestDiff < ActiveRecord::Base
ensure_commit_shas
save_commits
save_diffs
save
keep_around_commits
end
......@@ -56,7 +57,6 @@ class MergeRequestDiff < ActiveRecord::Base
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
self.base_commit_sha ||= find_base_sha
save
end
# Override head_commit_sha to keep compatibility with merge request diff
......@@ -195,7 +195,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_count
merge_request_diff_commits.size
super || merge_request_diff_commits.size
end
private
......@@ -264,13 +264,16 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :overflow if diff_collection.overflow?
end
update(new_attributes)
assign_attributes(new_attributes)
end
def save_commits
MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse)
merge_request_diff_commits.reload
# merge_request_diff_commits.reload is preferred way to reload associated
# objects but it returns cached result for some reason in this case
commits = merge_request_diff_commits(true)
self.commits_count = commits.size
end
def repository
......
......@@ -1166,7 +1166,7 @@ class Project < ActiveRecord::Base
def change_head(branch)
if repository.branch_exists?(branch)
repository.before_change_head
repository.write_ref('HEAD', "refs/heads/#{branch}")
repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}", shell: false)
repository.copy_gitattributes(branch)
repository.after_change_head
reload_default_branch
......
......@@ -267,7 +267,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
begin
write_ref(keep_around_ref_name(sha), sha)
raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false)
rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
rescue Rugged::OSError => ex
......@@ -281,10 +281,6 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
def write_ref(ref_path, sha)
rugged.references.create(ref_path, sha, force: true)
end
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
......
......@@ -2,7 +2,10 @@ class CheckGcpProjectBillingService
def execute(token)
client = GoogleApi::CloudPlatform::Client.new(token, nil)
client.projects_list.select do |project|
client.projects_get_billing_info(project.name).billingEnabled
begin
client.projects_get_billing_info(project.project_id).billing_enabled
rescue
end
end
end
end
......@@ -24,7 +24,7 @@ module Issues
@new_issue = create_new_issue
rewrite_notes
rewrite_award_emoji
rewrite_issue_award_emoji
add_note_moved_from
# Old issue tasks
......@@ -76,7 +76,7 @@ module Issues
end
def rewrite_notes
@old_issue.notes.find_each do |note|
@old_issue.notes_with_associations.find_each do |note|
new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue,
note: rewrite_content(new_note.note),
......@@ -84,13 +84,19 @@ module Issues
updated_at: note.updated_at }
new_note.update(new_params)
rewrite_award_emoji(note, new_note)
end
end
def rewrite_award_emoji
@old_issue.award_emoji.each do |award|
def rewrite_issue_award_emoji
rewrite_award_emoji(@old_issue, @new_issue)
end
def rewrite_award_emoji(old_awardable, new_awardable)
old_awardable.award_emoji.each do |award|
new_award = award.dup
new_award.awardable = @new_issue
new_award.awardable = new_awardable
new_award.save
end
end
......
......@@ -156,13 +156,9 @@ module MergeRequests
end
def assign_title_from_issue
return unless issue
return unless issue && issue.is_a?(Issue)
merge_request.title =
case issue
when Issue then "Resolve \"#{issue.title}\""
when ExternalIssue then "Resolve #{issue.title}"
end
merge_request.title = "Resolve \"#{issue.title}\""
end
def issue_iid
......
module MergeRequests
class RebaseService < MergeRequests::WorkingCopyBaseService
REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze
def execute(merge_request)
@merge_request = merge_request
if rebase
success
else
error('Failed to rebase. Should be done manually')
error(REBASE_ERROR)
end
end
......@@ -22,8 +24,8 @@ module MergeRequests
true
rescue => e
log_error('Failed to rebase branch:')
log_error(e.message, save_message_on_model: true)
log_error(REBASE_ERROR, save_message_on_model: true)
log_error(e.message)
false
end
end
......
......@@ -56,8 +56,6 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
%li
= link_to "Turn on multi edit", profile_preferences_path
- if current_user
%li
= link_to "Help", help_path
......
......@@ -5,8 +5,8 @@
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
.col-lg-4
%h4.prepend-top-0
GitLab multi file editor
%p Unlock an additional editing experience which makes it possible to edit and commit multiple files
Web IDE (Beta)
%p Enable the new web IDE on this device to make it possible to open and edit multiple files with a single commit
.col-lg-8.multi-file-editor-options
= label_tag do
.preview.append-bottom-10= image_tag "multi-editor-off.png"
......
......@@ -30,12 +30,13 @@
%li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do
#{ _('New file') }
%li
= link_to new_project_branch_path(@project) do
#{ _('New branch') }
%li
= link_to new_project_tag_path(@project) do
#{ _('New tag') }
- unless @project.empty_repo?
%li
= link_to new_project_branch_path(@project) do
#{ _('New branch') }
%li
= link_to new_project_tag_path(@project) do
#{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project)
%li
= link_to project_new_blob_path(@project, @project.default_branch || 'master') do
......
......@@ -4,11 +4,11 @@
= s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:')
%ul
%li
- link_to_kubernetes_engine = link_to(s_('ClusterIntegration|access to Google Kubernetes Engine'), 'https://console.cloud.google.com', target: '_blank', rel: 'noopener noreferrer')
- link_to_kubernetes_engine = link_to(s_('ClusterIntegration|access to Google Kubernetes Engine'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Your account must have %{link_to_kubernetes_engine}').html_safe % { link_to_kubernetes_engine: link_to_kubernetes_engine }
%li
- link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer')
- link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements }
%li
- link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer')
- link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project }
- @content_class = "limit-container-width" unless fluid_layout
- add_to_breadcrumbs "Clusters", project_clusters_path(@project)
- breadcrumb_title @cluster.id
- breadcrumb_title @cluster.name
- page_title _("Cluster")
- expanded = Rails.env.test?
......
......@@ -35,3 +35,6 @@
- if diff_file.mode_changed?
%small
#{diff_file.a_mode}#{diff_file.b_mode}
- if diff_file.stored_externally? && diff_file.external_storage == :lfs
%span.label.label-lfs.append-right-5 LFS
......@@ -10,6 +10,6 @@
%ul.dropdown-footer-list
%li
%button{ class: "create-new-protected-branch-button js-create-new-protected-branch", title: "New Protected Branch" }
%button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: "New Protected Branch" }
Create wildcard
%code
......@@ -10,6 +10,6 @@
%ul.dropdown-footer-list
%li
%button{ class: "create-new-protected-tag-button js-create-new-protected-tag", title: "New Protected Tag" }
%button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: "New Protected Tag" }
Create wildcard
%code
......@@ -4,7 +4,7 @@ class CheckGcpProjectBillingWorker
include ApplicationWorker
include ClusterQueue
LEASE_TIMEOUT = 15.seconds.to_i
LEASE_TIMEOUT = 3.seconds.to_i
SESSION_KEY_TIMEOUT = 5.minutes
BILLING_TIMEOUT = 1.hour
......@@ -23,13 +23,13 @@ class CheckGcpProjectBillingWorker
end
def self.redis_shared_state_key_for(token)
"gitlab:gcp:#{token.hash}:billing_enabled"
"gitlab:gcp:#{Digest::SHA1.hexdigest(token)}:billing_enabled"
end
def perform(token_key)
return unless token_key
token = self.get_session_token(token_key)
token = self.class.get_session_token(token_key)
return unless token
return unless try_obtain_lease_for(token)
......
---
title: Fix neutralCount computation to prevent negative values
merge_request: 4044
author:
type: fixed
---
title: Default merge request title is set correctly again when external issue tracker is activated
merge_request: 16356
author: Ben305
type: fixed
---
title: Store number of commits in merge_request_diffs table.
merge_request:
author:
type: performance
---
title: Add `pipelines` endpoint to merge requests API
merge_request: 15454
author: Tony Rom <thetonyrom@gmail.com>
type: added
---
title: Hide new branch and tag links for projects with an empty repo
merge_request:
author:
type: fixed
---
title: Display user friendly error message if rebase fails.
merge_request:
author:
type: fixed
---
title: Make project README containers wider on fixed layout
merge_request: 16181
author: Takuya Noguchi
type: fixed
---
title: Make modal dialog common for Groups tree app
merge_request: 16311
author:
type: fixed
---
title: Make rich blob viewer wider for PC
merge_request: 16262
author: Takuya Noguchi
type: fixed
---
title: Fix web ide user preferences copy and buttons
merge_request: 41789
author:
type: other
---
title: Add rake task to check integrity of uploaded files
merge_request:
author:
type: added
---
title: Fix bug where award emojis would be lost when moving issues between projects
merge_request:
author:
type: fixed
class AddCommitsCountToMergeRequestDiff < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'AddMergeRequestDiffCommitsCount'.freeze
BATCH_SIZE = 5000
DELAY_INTERVAL = 5.minutes.to_i
class MergeRequestDiff < ActiveRecord::Base
self.table_name = 'merge_request_diffs'
include ::EachBatch
end
disable_ddl_transaction!
def up
add_column :merge_request_diffs, :commits_count, :integer
say 'Populating the MergeRequestDiff `commits_count`'
queue_background_migration_jobs_by_range_at_intervals(MergeRequestDiff, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
remove_column :merge_request_diffs, :commits_count
end
end
......@@ -1397,6 +1397,7 @@ ActiveRecord::Schema.define(version: 20180105233807) do
t.string "real_size"
t.string "head_commit_sha"
t.string "start_commit_sha"
t.integer "commits_count"
end
add_index "merge_request_diffs", ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id", using: :btree
......
......@@ -66,3 +66,15 @@ On the sign in page there should now be a Crowd tab in the sign in form.
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
## Troubleshooting
If you see an error message like the one below when you sign in after Crowd authentication is configured, you may want to consult the Crowd administrator for the Crowd log file to know the exact cause:
```
could not authorize you from Crowd because invalid credentials
```
Please make sure the Crowd users who need to login to GitLab are authorized to [the application](#configure-a-new-crowd-application) in the step of **Authorisation**. This could be verified by try "Authentication test" for Crowd as of 2.11.
![Example Crowd application authorisation configuration](img/crowd_application_authorisation.png)
\ No newline at end of file
......@@ -76,6 +76,39 @@ Example output:
![gitlab:user:check_repos output](../img/raketasks/check_repos_output.png)
## Uploaded Files Integrity
The uploads check Rake task will loop through all uploads in the database
and run two checks to determine the integrity of each file:
1. Check if the file exist on the file system.
1. Check if the checksum of the file on the file system matches the checksum in the database.
**Omnibus Installation**
```
sudo gitlab-rake gitlab:uploads:check
```
**Source Installation**
```bash
sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production
```
This task also accepts some environment variables which you can use to override
certain values:
Variable | Type | Description
-------- | ---- | -----------
`BATCH` | integer | Specifies the size of the batch. Defaults to 200.
`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value.
`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value.
```bash
sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250
```
## LDAP Check
The LDAP check Rake task will test the bind_dn and password credentials
......
......@@ -474,6 +474,30 @@ Parameters:
}
```
## List MR pipelines
Get a list of merge request pipelines.
```
GET /projects/:id/merge_requests/:merge_request_iid/pipelines
```
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - The internal ID of the merge request
```json
[
{
"id": 77,
"sha": "959e04d7c7a30600c894bd3c0cd0e1ce7f42c11d",
"ref": "master",
"status": "success"
}
]
```
## Create MR
Creates a new merge request.
......
......@@ -10,25 +10,6 @@ They are written by members of the GitLab Team and by
Part of the articles listed below link to the [GitLab Blog](https://about.gitlab.com/blog/),
where they were originally published.
## Build, test, and deploy with GitLab CI/CD
Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/README.md):
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
| [Autoscaling GitLab Runners on AWS](runner_autoscale_aws/index.md) | Admin guide | 2017-11-24 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017-07-11 |
| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017-07-27 |
| [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/) | Tutorial | 2016-12-14 |
| [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/) | Tutorial | 2016-11-30 |
| [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) | Tutorial | 2016-10-12 |
| [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/) | Tutorial | 2016-08-11 |
| [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) | Technical overview | 2016-06-09 |
| [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) | Technical overview | 2016-05-23 |
| [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/) | Technical overview | 2017-05-15 |
| [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) | Tutorial | 2016-03-10 |
## GitLab Pages
Learn how to deploy a static website with [GitLab Pages](../user/project/pages/index.md#getting-started):
......
This diff is collapsed.
This diff is collapsed.
......@@ -2,93 +2,72 @@
comments: false
---
# GitLab CI Examples
# GitLab CI/CD Examples
A collection of `.gitlab-ci.yml` files is maintained at the [GitLab CI Yml project][gitlab-ci-templates].
If your favorite programming language or framework are missing we would love your help by sending a merge request
with a `.gitlab-ci.yml`.
A collection of `.gitlab-ci.yml` template files is maintained at the [GitLab CI/CD YAML project][gitlab-ci-templates]. When you create a new file via the UI,
GitLab will give you the option to choose one of the templates existent on this project.
If your favorite programming language or framework are missing we would love your
help by sending a merge request with a new `.gitlab-ci.yml` to this project.
Apart from those, here is an collection of tutorials and guides on setting up your CI pipeline:
There's also a collection of repositories with [example projects](https://gitlab.com/gitlab-examples) for various languages. You can fork an adjust them to your own needs.
## Languages, frameworks, OSs
### PHP
- **PHP**:
- [Testing a PHP application](php.md)
- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md)
- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md)
- **Ruby**: [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
- **Python**: [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
- **Java**: [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
- **Scala**: [Test a Scala application](test-scala-application.md)
- **Clojure**: [Test a Clojure application](test-clojure-application.md)
- **Elixir**:
- [Test a Phoenix application](test-phoenix-application.md)
- [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
- **iOS and macOS**:
- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
- [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/)
- **Android**: [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
- **Debian**: [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- **Maven**: [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
### Miscellaneous
- [Testing a PHP application](php.md)
- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md)
- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md)
### Ruby
- [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md)
### Python
- [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md)
### Java
- [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
### Scala
- [Test a Scala application](test-scala-application.md)
### Clojure
- [Test a Clojure application](test-clojure-application.md)
### Elixir
- [Test a Phoenix application](test-phoenix-application.md)
- [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
### iOS
- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
### Android
- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
- [Using `dpl` as deployment tool](deployment/README.md)
- [The `.gitlab-ci.yml` file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
### Code quality analysis
- [Analyze code quality with the Code Climate CLI](code_climate.md)
[Analyze code quality with the Code Climate CLI](code_climate.md).
### Static Application Security Testing (SAST)
- [Scan your code for vulnerabilities](sast.md)
- [Scan your Docker images for vulnerabilities](sast_docker.md)
### Dynamic Application Security Testing (DAST)
- [Scan your app for vulnerabilities](dast.md)
Scan your app for vulnerabilities with GitLab [Dynamic Application Security Testing (DAST)](dast.md).
### Browser Performance Testing with Sitespeed.io
- [Analyze browser performance with Sitespeed.io](browser_performance.md)
Analyze your [browser performance with Sitespeed.io](browser_performance.md).
### Other
- [Using `dpl` as deployment tool](deployment/README.md)
- [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples)
- [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
- [Continuous Deployment with GitLab: how to build and deploy a Debian Package with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md)
### GitLab CI/CD for Review Apps
## GitLab CI/CD for GitLab Pages
- [Example project](https://gitlab.com/gitlab-examples/review-apps-nginx/) that shows how to use GitLab CI/CD for [Review Apps](../review_apps/index.html).
- [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/)
- [Example projects](https://gitlab.com/pages)
- [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](../../user/project/pages/getting_started_part_four.md)
- [SSGs Part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/):
examples for Ruby-, NodeJS-, Python-, and GoLang-based SSGs
- [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
### GitLab CI/CD for GitLab Pages
See the documentation on [GitLab Pages](../../user/project/pages/index.md) for a complete overview.
## More
## Contributing
Contributions are very much welcomed! You can help your favorite programming
language and GitLab by sending a merge request with a guide for that language.
Contributions are very welcome! You can help your favorite programming
language users and GitLab by sending a merge request with a guide for that language.
You may want to apply for the [GitLab Community Writers Program](https://about.gitlab.com/community-writers/)
to get paid for writing complete articles for GitLab.
[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml
# Dynamic application security testing with GitLab CI/CD
NOTE: **Note:**
In order to use this tool, a [GitLab Enterprise Edition Ultimate][ee] license
is needed.
# Dynamic Application Security Testing with GitLab CI/CD
This example shows how to run
[Dynamic Application Security Testing (DAST)](https://en.wikipedia.org/wiki/Dynamic_program_analysis)
on your project's source code by using GitLab CI/CD.
DAST is using the popular open source tool
[OWASP ZAProxy](https://github.com/zaproxy/zaproxy) to perform an analysis.
All you need is a GitLab Runner with the Docker executor (the shared Runners on
GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
called `dast`:
......@@ -17,23 +16,20 @@ dast:
image: owasp/zap2docker-stable
script:
- mkdir /zap/wrk/
- /zap/zap-baseline.py -J gl-dast-report.json -t http://dzaporozhets.me/ || true
- /zap/zap-baseline.py -J gl-dast-report.json -t https://example.com || true
- cp /zap/wrk/gl-dast-report.json .
artifacts:
paths: [gl-dast-report.json]
```
DAST is using a popular open source tool
[OWASP ZAProxy](https://github.com/zaproxy/zaproxy) to perform an analysis.
The above example will create a `dast` job in your CI pipeline and will allow
you to download and analyze the report artifact in JSON format.
TIP: **Tip:**
Starting with GitLab Enterprise Edition Ultimate 10.4, this information will
Starting with [GitLab Enterprise Edition Ultimate][ee] 10.4, this information will
be automatically extracted and shown right in the merge request widget. To do
so, the CI job must be named `dast` and the artifact path must be
`gl-dast-report.json`.
[Learn more on application security testing results shown in merge requests](../../user/project/merge_requests/sast.md).
[Learn more on dynamic application security testing results shown in merge requests](../../user/project/merge_requests/dast.md).
[ee]: https://about.gitlab.com/gitlab-ee/
# Static application security testing with GitLab CI/CD
# Static Application Security Testing with GitLab CI/CD
NOTE: **Note:**
In order to use this tool, a [GitLab Enterprise Edition Ultimate][ee] license
......@@ -36,7 +36,7 @@ The results are sorted by the priority of the vulnerability:
1. Everything else
TIP: **Tip:**
Starting with GitLab Enterprise Edition Ultimate 10.3, this information will
Starting with [GitLab Enterprise Edition Ultimate][ee] 10.3, this information will
be automatically extracted and shown right in the merge request widget. To do
so, the CI job must be named `sast` and the artifact path must be
`gl-sast-report.json`.
......
# Static Application Security Testing for Docker containers with GitLab CI/CD
You can check your Docker images (or more precisely the containers) for known
vulnerabilities by using [Clair](https://github.com/coreos/clair) and
[clair-scanner](https://github.com/arminc/clair-scanner), two open source tools
for Vulnerability Static Analysis for containers.
All you need is a GitLab Runner with the Docker executor (the shared Runners on
GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
called `sast:container`:
```yaml
sast:container:
image: docker:latest
variables:
DOCKER_DRIVER: overlay2
## Define two new variables based on GitLab's CI/CD predefined variables
## https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables
CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
CI_APPLICATION_TAG: $CI_COMMIT_SHA
allow_failure: true
services:
- docker:dind
script:
- docker run -d --name db arminc/clair-db:latest
- docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- touch clair-whitelist.yml
- ./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]
```
The above example will create a `sast:container` job in your CI/CD pipeline, pull
the image from the [Container Registry](../../user/project/container_registry.md)
(whose name is defined from the two `CI_APPLICATION_` variables) and scan it
for possible vulnerabilities. The report will be saved as an artifact that you
can later download and analyze.
If you want to whitelist some specific vulnerabilities, you can do so by defining
them in a [YAML file](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file),
in our case its named `clair-whitelist.yml`.
TIP: **Tip:**
Starting with [GitLab Enterprise Edition Ultimate][ee] 10.4, this information will
be automatically extracted and shown right in the merge request widget. To do
so, the CI/CD job must be named `sast:container` and the artifact path must be
`gl-sast-container-report.json`.
[Learn more on application security testing results shown in merge requests](../../user/project/merge_requests/sast_docker.md).
[ee]: https://about.gitlab.com/gitlab-ee/
......@@ -368,7 +368,6 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
// bad
<component
bar="bar" />
```
#### Quotes
......@@ -520,7 +519,6 @@ On those a default key should not be provided.
1. Properties in a Vue Component:
Check [order of properties in components rule][vue-order].
#### Vue and Bootstrap
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
......
......@@ -25,7 +25,7 @@ It is possible to run end-to-end tests (eventually being run within a
the `package-qa` manual action, that should be present in a merge request
widget.
Mmanual action that starts end-to-end tests is also available in merge requests
Manual action that starts end-to-end tests is also available in merge requests
in Omnibus GitLab project.
Below you can read more about how to use it and how does it work.
......
......@@ -20,6 +20,7 @@ project in an easy and automatic way:
1. [Auto Test](#auto-test)
1. [Auto Code Quality](#auto-code-quality)
1. [Auto SAST (Static Application Security Testing)](#auto-sast)
1. [Auto SAST for Docker images](#auto-sast-for-docker-images)
1. [Auto Browser Performance Testing](#auto-browser-performance-testing)
1. [Auto Review Apps](#auto-review-apps)
1. [Auto Deploy](#auto-deploy)
......@@ -209,17 +210,18 @@ check out.
Any security warnings are also [shown in the merge request widget](../../user/project/merge_requests/sast.md).
### Auto SAST
### Auto SAST for Docker images
> Introduced in [GitLab Enterprise Edition Ultimate][ee] 10.3.
> Introduced in GitLab 10.4.
Static Application Security Testing (SAST) uses the
[gl-sast Docker image](https://gitlab.com/gitlab-org/gl-sast) to run static
analysis on the current code and checks for potential security issues. Once the
report is created, it's uploaded as an artifact which you can later download and
Vulnerability Static Analysis for containers uses
[Clair](https://github.com/coreos/clair) to run static analysis on a
Docker image and checks for potential security issues. Once the report is
created, it's uploaded as an artifact which you can later download and
check out.
Any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
In GitLab Enterprise Edition Ultimate, any security warnings are also
[shown in the merge request widget](../../user/project/merge_requests/sast_docker.md).
### Auto Browser Performance Testing
......@@ -233,7 +235,8 @@ Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://h
/direction
```
In GitLab Enterprise Edition Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html).
In GitLab Enterprise Edition Premium, performance differences between the source
and target branches are [shown in the merge request widget](../../user/project/merge_requests/browser_performance_testing.md).
### Auto Review Apps
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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