Commit 62ded594 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-07-18

# Conflicts:
#	app/assets/stylesheets/framework/variables.scss
#	locale/gitlab.pot

[ci skip]
parents f76828db 9bdc9b1a
...@@ -85,6 +85,9 @@ export default { ...@@ -85,6 +85,9 @@ export default {
} }
return __('Show latest version'); return __('Show latest version');
}, },
canCurrentUserFork() {
return this.currentUser.canFork === true && this.currentUser.canCreateMergeRequest;
},
}, },
watch: { watch: {
diffViewType() { diffViewType() {
...@@ -192,7 +195,7 @@ export default { ...@@ -192,7 +195,7 @@ export default {
v-for="file in diffFiles" v-for="file in diffFiles"
:key="file.newPath" :key="file.newPath"
:file="file" :file="file"
:current-user="currentUser" :can-current-user-fork="canCurrentUserFork"
/> />
</div> </div>
<no-changes v-else /> <no-changes v-else />
......
...@@ -18,8 +18,8 @@ export default { ...@@ -18,8 +18,8 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
currentUser: { canCurrentUserFork: {
type: Object, type: Boolean,
required: true, required: true,
}, },
}, },
...@@ -87,7 +87,7 @@ export default { ...@@ -87,7 +87,7 @@ export default {
class="diff-file file-holder" class="diff-file file-holder"
> >
<diff-file-header <diff-file-header
:current-user="currentUser" :can-current-user-fork="canCurrentUserFork"
:diff-file="file" :diff-file="file"
:collapsible="true" :collapsible="true"
:expanded="!isCollapsed" :expanded="!isCollapsed"
......
...@@ -39,8 +39,8 @@ export default { ...@@ -39,8 +39,8 @@ export default {
required: false, required: false,
default: true, default: true,
}, },
currentUser: { canCurrentUserFork: {
type: Object, type: Boolean,
required: true, required: true,
}, },
}, },
...@@ -228,7 +228,7 @@ export default { ...@@ -228,7 +228,7 @@ export default {
<edit-button <edit-button
v-if="!diffFile.deletedFile" v-if="!diffFile.deletedFile"
:current-user="currentUser" :can-current-user-fork="canCurrentUserFork"
:edit-path="diffFile.editPath" :edit-path="diffFile.editPath"
:can-modify-blob="diffFile.canModifyBlob" :can-modify-blob="diffFile.canModifyBlob"
@showForkMessage="showForkMessage" @showForkMessage="showForkMessage"
......
...@@ -13,12 +13,8 @@ export default { ...@@ -13,12 +13,8 @@ export default {
noteForm, noteForm,
}, },
props: { props: {
diffFile: { diffFileHash: {
type: Object, type: String,
required: true,
},
diffLines: {
type: Array,
required: true, required: true,
}, },
line: { line: {
...@@ -40,6 +36,7 @@ export default { ...@@ -40,6 +36,7 @@ export default {
noteableData: state => state.notes.noteableData, noteableData: state => state.notes.noteableData,
diffViewType: state => state.diffs.diffViewType, diffViewType: state => state.diffs.diffViewType,
}), }),
...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters(['isLoggedIn', 'noteableType', 'getNoteableData', 'getNotesDataByProp']), ...mapGetters(['isLoggedIn', 'noteableType', 'getNoteableData', 'getNotesDataByProp']),
}, },
mounted() { mounted() {
...@@ -68,13 +65,14 @@ export default { ...@@ -68,13 +65,14 @@ export default {
}); });
}, },
handleSaveNote(note) { handleSaveNote(note) {
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
const postData = getNoteFormData({ const postData = getNoteFormData({
note, note,
noteableData: this.noteableData, noteableData: this.noteableData,
noteableType: this.noteableType, noteableType: this.noteableType,
noteTargetLine: this.noteTargetLine, noteTargetLine: this.noteTargetLine,
diffViewType: this.diffViewType, diffViewType: this.diffViewType,
diffFile: this.diffFile, diffFile: selectedDiffFile,
linePosition: this.position, linePosition: this.position,
}); });
......
...@@ -24,8 +24,12 @@ export default { ...@@ -24,8 +24,12 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
diffFile: { fileHash: {
type: Object, type: String,
required: true,
},
contextLinesPath: {
type: String,
required: true, required: true,
}, },
diffViewType: { diffViewType: {
...@@ -120,14 +124,14 @@ export default { ...@@ -120,14 +124,14 @@ export default {
:class="classNameMap" :class="classNameMap"
> >
<diff-line-gutter-content <diff-line-gutter-content
:file-hash="diffFile.fileHash" :file-hash="fileHash"
:context-lines-path="contextLinesPath"
:line-type="normalizedLine.type" :line-type="normalizedLine.type"
:line-code="normalizedLine.lineCode" :line-code="normalizedLine.lineCode"
:line-position="linePosition" :line-position="linePosition"
:line-number="lineNumber" :line-number="lineNumber"
:meta-data="normalizedLine.metaData" :meta-data="normalizedLine.metaData"
:show-comment-button="showCommentButton" :show-comment-button="showCommentButton"
:context-lines-path="diffFile.contextLinesPath"
:is-bottom="isBottom" :is-bottom="isBottom"
:is-match-line="isMatchLine" :is-match-line="isMatchLine"
:is-context-line="isContentLine" :is-context-line="isContentLine"
......
...@@ -5,8 +5,8 @@ export default { ...@@ -5,8 +5,8 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
currentUser: { canCurrentUserFork: {
type: Object, type: Boolean,
required: true, required: true,
}, },
canModifyBlob: { canModifyBlob: {
...@@ -17,12 +17,12 @@ export default { ...@@ -17,12 +17,12 @@ export default {
}, },
methods: { methods: {
handleEditClick(evt) { handleEditClick(evt) {
if (!this.currentUser || this.canModifyBlob) { if (!this.canCurrentUserFork || this.canModifyBlob) {
// if we can Edit, do default Edit button behavior // if we can Edit, do default Edit button behavior
return; return;
} }
if (this.currentUser.canFork && this.currentUser.canCreateMergeRequest) { if (this.canCurrentUserFork) {
evt.preventDefault(); evt.preventDefault();
this.$emit('showForkMessage'); this.$emit('showForkMessage');
} }
......
...@@ -13,12 +13,8 @@ export default { ...@@ -13,12 +13,8 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
diffFile: { diffFileHash: {
type: Object, type: String,
required: true,
},
diffLines: {
type: Array,
required: true, required: true,
}, },
lineIndex: { lineIndex: {
...@@ -58,10 +54,9 @@ export default { ...@@ -58,10 +54,9 @@ export default {
/> />
<diff-line-note-form <diff-line-note-form
v-if="diffLineCommentForms[line.lineCode]" v-if="diffLineCommentForms[line.lineCode]"
:diff-file="diffFile" :diff-file-hash="diffFileHash"
:diff-lines="diffLines"
:line="line" :line="line"
:note-target-line="diffLines[lineIndex]" :note-target-line="line"
/> />
</div> </div>
</td> </td>
......
...@@ -16,8 +16,12 @@ export default { ...@@ -16,8 +16,12 @@ export default {
DiffTableCell, DiffTableCell,
}, },
props: { props: {
diffFile: { fileHash: {
type: Object, type: String,
required: true,
},
contextLinesPath: {
type: String,
required: true, required: true,
}, },
line: { line: {
...@@ -50,7 +54,7 @@ export default { ...@@ -50,7 +54,7 @@ export default {
inlineRowId() { inlineRowId() {
const { lineCode, oldLine, newLine } = this.line; const { lineCode, oldLine, newLine } = this.line;
return lineCode || `${this.diffFile.fileHash}_${oldLine}_${newLine}`; return lineCode || `${this.fileHash}_${oldLine}_${newLine}`;
}, },
}, },
created() { created() {
...@@ -78,7 +82,8 @@ export default { ...@@ -78,7 +82,8 @@ export default {
@mouseout="handleMouseMove" @mouseout="handleMouseMove"
> >
<diff-table-cell <diff-table-cell
:diff-file="diffFile" :file-hash="fileHash"
:context-lines-path="contextLinesPath"
:line="line" :line="line"
:line-type="oldLineType" :line-type="oldLineType"
:is-bottom="isBottom" :is-bottom="isBottom"
...@@ -87,7 +92,8 @@ export default { ...@@ -87,7 +92,8 @@ export default {
class="diff-line-num old_line" class="diff-line-num old_line"
/> />
<diff-table-cell <diff-table-cell
:diff-file="diffFile" :file-hash="fileHash"
:context-lines-path="contextLinesPath"
:line="line" :line="line"
:line-type="newLineType" :line-type="newLineType"
:is-bottom="isBottom" :is-bottom="isBottom"
......
...@@ -60,15 +60,15 @@ export default { ...@@ -60,15 +60,15 @@ export default {
v-for="(line, index) in normalizedDiffLines" v-for="(line, index) in normalizedDiffLines"
> >
<inline-diff-table-row <inline-diff-table-row
:diff-file="diffFile" :file-hash="diffFile.fileHash"
:context-lines-path="diffFile.contextLinesPath"
:line="line" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:key="line.lineCode" :key="line.lineCode"
/> />
<inline-diff-comment-row <inline-diff-comment-row
v-if="shouldRenderCommentRow(line)" v-if="shouldRenderCommentRow(line)"
:diff-file="diffFile" :diff-file-hash="diffFile.fileHash"
:diff-lines="normalizedDiffLines"
:line="line" :line="line"
:line-index="index" :line-index="index"
:key="index" :key="index"
......
...@@ -13,12 +13,8 @@ export default { ...@@ -13,12 +13,8 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
diffFile: { diffFileHash: {
type: Object, type: String,
required: true,
},
diffLines: {
type: Array,
required: true, required: true,
}, },
lineIndex: { lineIndex: {
...@@ -91,10 +87,9 @@ export default { ...@@ -91,10 +87,9 @@ export default {
<diff-line-note-form <diff-line-note-form
v-if="diffLineCommentForms[leftLineCode] && v-if="diffLineCommentForms[leftLineCode] &&
diffLineCommentForms[leftLineCode]" diffLineCommentForms[leftLineCode]"
:diff-file="diffFile" :diff-file-hash="diffFileHash"
:diff-lines="diffLines"
:line="line.left" :line="line.left"
:note-target-line="diffLines[lineIndex].left" :note-target-line="line.left"
position="left" position="left"
/> />
</td> </td>
...@@ -112,10 +107,9 @@ export default { ...@@ -112,10 +107,9 @@ export default {
<diff-line-note-form <diff-line-note-form
v-if="diffLineCommentForms[rightLineCode] && v-if="diffLineCommentForms[rightLineCode] &&
diffLineCommentForms[rightLineCode] && line.right.type" diffLineCommentForms[rightLineCode] && line.right.type"
:diff-file="diffFile" :diff-file-hash="diffFileHash"
:diff-lines="diffLines"
:line="line.right" :line="line.right"
:note-target-line="diffLines[lineIndex].right" :note-target-line="line.right"
position="right" position="right"
/> />
</td> </td>
......
...@@ -19,8 +19,12 @@ export default { ...@@ -19,8 +19,12 @@ export default {
DiffTableCell, DiffTableCell,
}, },
props: { props: {
diffFile: { fileHash: {
type: Object, type: String,
required: true,
},
contextLinesPath: {
type: String,
required: true, required: true,
}, },
line: { line: {
...@@ -103,7 +107,8 @@ export default { ...@@ -103,7 +107,8 @@ export default {
@mouseout="handleMouseMove" @mouseout="handleMouseMove"
> >
<diff-table-cell <diff-table-cell
:diff-file="diffFile" :file-hash="fileHash"
:context-lines-path="contextLinesPath"
:line="line" :line="line"
:line-type="oldLineType" :line-type="oldLineType"
:line-position="linePositionLeft" :line-position="linePositionLeft"
...@@ -123,7 +128,8 @@ export default { ...@@ -123,7 +128,8 @@ export default {
> >
</td> </td>
<diff-table-cell <diff-table-cell
:diff-file="diffFile" :file-hash="fileHash"
:context-lines-path="contextLinesPath"
:line="line" :line="line"
:line-type="newLineType" :line-type="newLineType"
:line-position="linePositionRight" :line-position="linePositionRight"
......
...@@ -93,17 +93,17 @@ export default { ...@@ -93,17 +93,17 @@ export default {
v-for="(line, index) in parallelDiffLines" v-for="(line, index) in parallelDiffLines"
> >
<parallel-diff-table-row <parallel-diff-table-row
:diff-file="diffFile" :file-hash="diffFile.fileHash"
:context-lines-path="diffFile.contextLinesPath"
:line="line" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:key="index" :key="index"
/> />
<parallel-diff-comment-row <parallel-diff-comment-row
v-if="shouldRenderCommentRow(line)" v-if="shouldRenderCommentRow(line)"
:key="line.left.lineCode || line.right.lineCode" :key="`dcr-${index}`"
:line="line" :line="line"
:diff-file="diffFile" :diff-file-hash="diffFile.fileHash"
:diff-lines="parallelDiffLines"
:line-index="index" :line-index="index"
/> />
</template> </template>
......
...@@ -57,4 +57,8 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) = ...@@ -57,4 +57,8 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
) || []; ) || [];
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests // prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
export const getDiffFileByHash = state => fileHash =>
state.diffFiles.find(file => file.fileHash === fileHash);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -38,6 +38,7 @@ export default { ...@@ -38,6 +38,7 @@ export default {
<button <button
:aria-label="label" :aria-label="label"
type="button" type="button"
class="btn-blank"
@click.stop.prevent="clicked" @click.stop.prevent="clicked"
> >
<icon <icon
......
import _ from 'underscore'; import _ from 'underscore';
function sortMetrics(metrics) { function sortMetrics(metrics) {
return _.chain(metrics).sortBy('title').sortBy('weight').value(); return _.chain(metrics)
.sortBy('title')
.sortBy('weight')
.value();
} }
function normalizeMetrics(metrics) { function normalizeMetrics(metrics) {
...@@ -39,7 +42,9 @@ export default class MonitoringStore { ...@@ -39,7 +42,9 @@ export default class MonitoringStore {
} }
storeEnvironmentsData(environmentsData = []) { storeEnvironmentsData(environmentsData = []) {
this.environmentsData = environmentsData; this.environmentsData = environmentsData.filter(
environment => !!environment.latest.last_deployment,
);
} }
getMetricsCount() { getMetricsCount() {
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import imageDiffHelper from '~/image_diff/helpers/index'; import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
export default { export default {
components: { components: {
DiffFileHeader, DiffFileHeader,
SkeletonLoadingContainer, SkeletonLoadingContainer,
},
props: {
discussion: {
type: Object,
required: true,
}, },
props: { },
discussion: { data() {
type: Object, return {
required: true, error: false,
}, };
},
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
}),
hasTruncatedDiffLines() {
return this.discussion.truncatedDiffLines && this.discussion.truncatedDiffLines.length !== 0;
}, },
data() { isDiscussionsExpanded() {
return { return true; // TODO: @fatihacet - Fix this.
error: false,
};
}, },
computed: { isCollapsed() {
...mapState({ return this.diffFile.collapsed || false;
noteableData: state => state.notes.noteableData, },
}), isImageDiff() {
hasTruncatedDiffLines() { return !this.diffFile.text;
return this.discussion.truncatedDiffLines && },
this.discussion.truncatedDiffLines.length !== 0; diffFileClass() {
}, const { text } = this.diffFile;
isDiscussionsExpanded() { return text ? 'text-file' : 'js-image-file';
return true; // TODO: @fatihacet - Fix this. },
}, diffFile() {
isCollapsed() { return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true });
return this.diffFile.collapsed || false;
},
isImageDiff() {
return !this.diffFile.text;
},
diffFileClass() {
const { text } = this.diffFile;
return text ? 'text-file' : 'js-image-file';
},
diffFile() {
return convertObjectPropsToCamelCase(this.discussion.diffFile, { deep: true });
},
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
currentUser() {
return this.noteableData.current_user;
},
userColorScheme() {
return window.gon.user_color_scheme;
},
normalizedDiffLines() {
if (this.discussion.truncatedDiffLines) {
return this.discussion.truncatedDiffLines.map(line =>
trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)),
);
}
return [];
},
}, },
mounted() { imageDiffHtml() {
if (this.isImageDiff) { return this.discussion.imageDiffHtml;
const canCreateNote = false; },
const renderCommentBadge = true; userColorScheme() {
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge); return window.gon.user_color_scheme;
} else if (!this.hasTruncatedDiffLines) { },
this.fetchDiff(); normalizedDiffLines() {
if (this.discussion.truncatedDiffLines) {
return this.discussion.truncatedDiffLines.map(line =>
trimFirstCharOfLineContent(convertObjectPropsToCamelCase(line)),
);
} }
return [];
},
},
mounted() {
if (this.isImageDiff) {
const canCreateNote = false;
const renderCommentBadge = true;
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
} else if (!this.hasTruncatedDiffLines) {
this.fetchDiff();
}
},
methods: {
...mapActions(['fetchDiscussionDiffLines']),
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
}, },
methods: { fetchDiff() {
...mapActions(['fetchDiscussionDiffLines']), this.error = false;
rowTag(html) { this.fetchDiscussionDiffLines(this.discussion)
return html.outerHTML ? 'tr' : 'template'; .then(this.highlight)
}, .catch(() => {
fetchDiff() { this.error = true;
this.error = false; });
this.fetchDiscussionDiffLines(this.discussion)
.then(this.highlight)
.catch(() => {
this.error = true;
});
},
}, },
}; },
};
</script> </script>
<template> <template>
...@@ -99,7 +95,7 @@ ...@@ -99,7 +95,7 @@
> >
<diff-file-header <diff-file-header
:diff-file="diffFile" :diff-file="diffFile"
:current-user="currentUser" :can-current-user-fork="false"
:discussions-expanded="isDiscussionsExpanded" :discussions-expanded="isDiscussionsExpanded"
:expanded="!isCollapsed" :expanded="!isCollapsed"
/> />
......
...@@ -302,10 +302,13 @@ $system-footer-height: $system-header-height; ...@@ -302,10 +302,13 @@ $system-footer-height: $system-header-height;
$flash-height: 52px; $flash-height: 52px;
$context-header-height: 60px; $context-header-height: 60px;
$breadcrumb-min-height: 48px; $breadcrumb-min-height: 48px;
<<<<<<< HEAD
$issue-box-upcoming-bg: #8f8f8f; $issue-box-upcoming-bg: #8f8f8f;
$pages-group-name-color: #4c4e54; $pages-group-name-color: #4c4e54;
$ldap-members-override-bg: $orange-50; $ldap-members-override-bg: $orange-50;
=======
>>>>>>> upstream/master
/* /*
* Common component specific colors * Common component specific colors
......
...@@ -551,6 +551,7 @@ ...@@ -551,6 +551,7 @@
@include media-breakpoint-up(lg) { @include media-breakpoint-up(lg) {
.branch-actions { .branch-actions {
align-self: center; align-self: center;
margin-left: $gl-padding;
} }
} }
} }
......
...@@ -210,7 +210,7 @@ class Note < ActiveRecord::Base ...@@ -210,7 +210,7 @@ class Note < ActiveRecord::Base
end end
def hook_attrs def hook_attrs
attributes Gitlab::HookData::NoteBuilder.new(self).build
end end
def for_commit? def for_commit?
......
...@@ -60,7 +60,7 @@ class WikiPage ...@@ -60,7 +60,7 @@ class WikiPage
attr_accessor :attributes attr_accessor :attributes
def hook_attrs def hook_attrs
attributes Gitlab::HookData::WikiPageBuilder.new(self).build
end end
def initialize(wiki, page = nil, persisted = false) def initialize(wiki, page = nil, persisted = false)
......
# frozen_string_literal: true
module ApplicationSettings module ApplicationSettings
class BaseService < ::BaseService class BaseService < ::BaseService
def initialize(application_setting, user, params = {}) def initialize(application_setting, user, params = {})
......
# frozen_string_literal: true
module ApplicationSettings module ApplicationSettings
class UpdateService < ApplicationSettings::BaseService class UpdateService < ApplicationSettings::BaseService
prepend EE::ApplicationSettings::UpdateService prepend EE::ApplicationSettings::UpdateService
......
# frozen_string_literal: true
module Applications module Applications
class CreateService class CreateService
prepend ::EE::Applications::CreateService prepend ::EE::Applications::CreateService
......
# frozen_string_literal: true
module Auth module Auth
class ContainerRegistryAuthenticationService < BaseService class ContainerRegistryAuthenticationService < BaseService
AUDIENCE = 'container_registry'.freeze AUDIENCE = 'container_registry'.freeze
......
# frozen_string_literal: true
module Badges module Badges
class BaseService class BaseService
protected protected
......
# frozen_string_literal: true
module Badges module Badges
class BuildService < Badges::BaseService class BuildService < Badges::BaseService
# returns the created badge # returns the created badge
......
# frozen_string_literal: true
module Badges module Badges
class CreateService < Badges::BaseService class CreateService < Badges::BaseService
# returns the created badge # returns the created badge
......
# frozen_string_literal: true
module Badges module Badges
class UpdateService < Badges::BaseService class UpdateService < Badges::BaseService
# returns the updated badge # returns the updated badge
......
# frozen_string_literal: true
module Boards module Boards
class BaseService < ::BaseService class BaseService < ::BaseService
prepend EE::Boards::BaseService prepend EE::Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
class CreateService < Boards::BaseService class CreateService < Boards::BaseService
prepend EE::Boards::CreateService prepend EE::Boards::CreateService
......
# frozen_string_literal: true
module Boards module Boards
module Issues module Issues
class CreateService < Boards::BaseService class CreateService < Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
module Issues module Issues
class ListService < Boards::BaseService class ListService < Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
module Issues module Issues
class MoveService < Boards::BaseService class MoveService < Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
class ListService < Boards::BaseService class ListService < Boards::BaseService
prepend EE::Boards::ListService prepend EE::Boards::ListService
......
# frozen_string_literal: true
module Boards module Boards
module Lists module Lists
class CreateService < Boards::BaseService class CreateService < Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
module Lists module Lists
class DestroyService < Boards::BaseService class DestroyService < Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
module Lists module Lists
class GenerateService < Boards::BaseService class GenerateService < Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
module Lists module Lists
class ListService < Boards::BaseService class ListService < Boards::BaseService
......
# frozen_string_literal: true
module Boards module Boards
module Lists module Lists
class MoveService < Boards::BaseService class MoveService < Boards::BaseService
......
# frozen_string_literal: true
module ChatNames module ChatNames
class AuthorizeUserService class AuthorizeUserService
include Gitlab::Routing include Gitlab::Routing
......
# frozen_string_literal: true
module ChatNames module ChatNames
class FindUserService class FindUserService
def initialize(service, params) def initialize(service, params)
......
# frozen_string_literal: true
module Ci module Ci
class CreatePipelineScheduleService < BaseService class CreatePipelineScheduleService < BaseService
def execute def execute
......
# frozen_string_literal: true
module Ci module Ci
class CreatePipelineService < BaseService class CreatePipelineService < BaseService
attr_reader :pipeline attr_reader :pipeline
......
# frozen_string_literal: true
module Ci module Ci
## ##
# We call this service everytime we persist a CI/CD job. # We call this service everytime we persist a CI/CD job.
......
# frozen_string_literal: true
module Ci module Ci
class ExtractSectionsFromBuildTraceService < BaseService class ExtractSectionsFromBuildTraceService < BaseService
def execute(build) def execute(build)
......
# frozen_string_literal: true
## ##
# TODO: # TODO:
# Almost components in this class were copied from app/models/project_services/kubernetes_service.rb # Almost components in this class were copied from app/models/project_services/kubernetes_service.rb
......
# frozen_string_literal: true
module Ci module Ci
class PipelineTriggerService < BaseService class PipelineTriggerService < BaseService
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
......
# frozen_string_literal: true
module Ci module Ci
class PlayBuildService < ::BaseService class PlayBuildService < ::BaseService
def execute(build) def execute(build)
......
# frozen_string_literal: true
module Ci module Ci
class ProcessPipelineService < BaseService class ProcessPipelineService < BaseService
attr_reader :pipeline attr_reader :pipeline
......
# frozen_string_literal: true
module Ci module Ci
# This class responsible for assigning # This class responsible for assigning
# proper pending build to runner on runner API request # proper pending build to runner on runner API request
......
# frozen_string_literal: true
module Ci module Ci
class RetryBuildService < ::BaseService class RetryBuildService < ::BaseService
CLONE_ACCESSORS = %i[pipeline project ref tag options commands name CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
......
# frozen_string_literal: true
module Ci module Ci
class RetryPipelineService < ::BaseService class RetryPipelineService < ::BaseService
include Gitlab::OptimisticLocking include Gitlab::OptimisticLocking
......
# frozen_string_literal: true
module Ci module Ci
class StopEnvironmentsService < BaseService class StopEnvironmentsService < BaseService
attr_reader :ref attr_reader :ref
......
# frozen_string_literal: true
module Ci module Ci
class UpdateBuildQueueService class UpdateBuildQueueService
def execute(build) def execute(build)
......
# frozen_string_literal: true
module Ci module Ci
class UpdateRunnerService class UpdateRunnerService
attr_reader :runner attr_reader :runner
......
# frozen_string_literal: true
module Clusters module Clusters
module Applications module Applications
class BaseHelmService class BaseHelmService
......
# frozen_string_literal: true
module Clusters module Clusters
module Applications module Applications
class CheckIngressIpAddressService < BaseHelmService class CheckIngressIpAddressService < BaseHelmService
......
# frozen_string_literal: true
module Clusters module Clusters
module Applications module Applications
class CheckInstallationProgressService < BaseHelmService class CheckInstallationProgressService < BaseHelmService
......
# frozen_string_literal: true
module Clusters module Clusters
module Applications module Applications
class InstallService < BaseHelmService class InstallService < BaseHelmService
......
# frozen_string_literal: true
module Clusters module Clusters
module Applications module Applications
class ScheduleInstallationService < ::BaseService class ScheduleInstallationService < ::BaseService
......
# frozen_string_literal: true
module Clusters module Clusters
class CreateService < BaseService class CreateService < BaseService
prepend EE::Clusters::CreateService prepend EE::Clusters::CreateService
......
# frozen_string_literal: true
module Clusters module Clusters
module Gcp module Gcp
class FetchOperationService class FetchOperationService
......
# frozen_string_literal: true
module Clusters module Clusters
module Gcp module Gcp
class FinalizeCreationService class FinalizeCreationService
......
# frozen_string_literal: true
module Clusters module Clusters
module Gcp module Gcp
class ProvisionService class ProvisionService
......
# frozen_string_literal: true
module Clusters module Clusters
module Gcp module Gcp
class VerifyProvisionStatusService class VerifyProvisionStatusService
......
# frozen_string_literal: true
module Clusters module Clusters
class UpdateService < BaseService class UpdateService < BaseService
def execute(cluster) def execute(cluster)
......
# frozen_string_literal: true
module Commits module Commits
class ChangeService < Commits::CreateService class ChangeService < Commits::CreateService
def initialize(*args) def initialize(*args)
......
# frozen_string_literal: true
module Commits module Commits
class CherryPickService < ChangeService class CherryPickService < ChangeService
def create_commit! def create_commit!
......
# frozen_string_literal: true
module Commits module Commits
class CreateService < ::BaseService class CreateService < ::BaseService
prepend EE::Commits::CreateService prepend EE::Commits::CreateService
......
# frozen_string_literal: true
module Commits module Commits
class RevertService < ChangeService class RevertService < ChangeService
def create_commit! def create_commit!
......
# frozen_string_literal: true
# #
# Concern that helps with getting an exclusive lease for running a block # Concern that helps with getting an exclusive lease for running a block
# of code. # of code.
......
# frozen_string_literal: true
module Issues module Issues
module ResolveDiscussions module ResolveDiscussions
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
......
# frozen_string_literal: true
module UpdateVisibilityLevel module UpdateVisibilityLevel
def valid_visibility_level_change?(target, new_visibility) def valid_visibility_level_change?(target, new_visibility)
# check that user is allowed to set specified visibility_level # check that user is allowed to set specified visibility_level
......
# frozen_string_literal: true
module Users module Users
module NewUserNotifier module NewUserNotifier
def notify_new_user(user, reset_token) def notify_new_user(user, reset_token)
......
# frozen_string_literal: true
module Users module Users
module ParticipableService module ParticipableService
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
module DeployKeys module DeployKeys
class CreateService < Keys::BaseService class CreateService < Keys::BaseService
def execute def execute
......
# frozen_string_literal: true
module DeployTokens module DeployTokens
class CreateService < BaseService class CreateService < BaseService
def execute def execute
......
# frozen_string_literal: true
module Discussions module Discussions
class BaseService < ::BaseService class BaseService < ::BaseService
end end
......
# frozen_string_literal: true
module Discussions module Discussions
class ResolveService < Discussions::BaseService class ResolveService < Discussions::BaseService
def execute(one_or_more_discussions) def execute(one_or_more_discussions)
......
# frozen_string_literal: true
module Discussions module Discussions
class UpdateDiffPositionService < BaseService class UpdateDiffPositionService < BaseService
def execute(discussion) def execute(discussion)
......
# frozen_string_literal: true
module Emails module Emails
class BaseService class BaseService
def initialize(current_user, params = {}) def initialize(current_user, params = {})
......
# frozen_string_literal: true
module Emails module Emails
class ConfirmService < ::Emails::BaseService class ConfirmService < ::Emails::BaseService
def execute(email) def execute(email)
......
# frozen_string_literal: true
module Emails module Emails
class CreateService < ::Emails::BaseService class CreateService < ::Emails::BaseService
prepend ::EE::Emails::CreateService prepend ::EE::Emails::CreateService
......
# frozen_string_literal: true
module Emails module Emails
class DestroyService < ::Emails::BaseService class DestroyService < ::Emails::BaseService
prepend ::EE::Emails::DestroyService prepend ::EE::Emails::DestroyService
......
# frozen_string_literal: true
module Events module Events
class RenderService < BaseRenderer class RenderService < BaseRenderer
def execute(events, atom_request: false) def execute(events, atom_request: false)
......
# frozen_string_literal: true
module Files module Files
class BaseService < Commits::CreateService class BaseService < Commits::CreateService
FileChangedError = Class.new(StandardError) FileChangedError = Class.new(StandardError)
......
# frozen_string_literal: true
module Files module Files
class CreateDirService < Files::BaseService class CreateDirService < Files::BaseService
def create_commit! def create_commit!
......
# frozen_string_literal: true
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def create_commit! def create_commit!
......
# frozen_string_literal: true
module Files module Files
class DeleteService < Files::BaseService class DeleteService < Files::BaseService
def create_commit! def create_commit!
......
# frozen_string_literal: true
module Files module Files
class MultiService < Files::BaseService class MultiService < Files::BaseService
UPDATE_FILE_ACTIONS = %w(update move delete).freeze UPDATE_FILE_ACTIONS = %w(update move delete).freeze
......
# frozen_string_literal: true
module Files module Files
class UpdateService < Files::BaseService class UpdateService < Files::BaseService
def create_commit! def create_commit!
......
# frozen_string_literal: true
module GpgKeys module GpgKeys
class CreateService < Keys::BaseService class CreateService < Keys::BaseService
def execute def execute
......
# frozen_string_literal: true
module Groups module Groups
class BaseService < ::BaseService class BaseService < ::BaseService
attr_accessor :group, :current_user, :params attr_accessor :group, :current_user, :params
......
# frozen_string_literal: true
module Groups module Groups
class CreateService < Groups::BaseService class CreateService < Groups::BaseService
prepend ::EE::Groups::CreateService prepend ::EE::Groups::CreateService
......
# frozen_string_literal: true
module Groups module Groups
class DestroyService < Groups::BaseService class DestroyService < Groups::BaseService
prepend ::EE::Groups::DestroyService prepend ::EE::Groups::DestroyService
......
# frozen_string_literal: true
module Groups module Groups
class NestedCreateService < Groups::BaseService class NestedCreateService < Groups::BaseService
attr_reader :group_path, :visibility_level attr_reader :group_path, :visibility_level
......
# frozen_string_literal: true
module Groups module Groups
class TransferService < Groups::BaseService class TransferService < Groups::BaseService
ERROR_MESSAGES = { ERROR_MESSAGES = {
......
# frozen_string_literal: true
module Groups module Groups
class UpdateService < Groups::BaseService class UpdateService < Groups::BaseService
include UpdateVisibilityLevel include UpdateVisibilityLevel
......
# frozen_string_literal: true
module Issuable module Issuable
class BulkUpdateService < IssuableBaseService class BulkUpdateService < IssuableBaseService
def execute(type) def execute(type)
......
# frozen_string_literal: true
module Issuable module Issuable
class CommonSystemNotesService < ::BaseService class CommonSystemNotesService < ::BaseService
prepend EE::Issuable::CommonSystemNotesService prepend EE::Issuable::CommonSystemNotesService
......
# frozen_string_literal: true
module Issuable module Issuable
class DestroyService < IssuableBaseService class DestroyService < IssuableBaseService
def execute(issuable) def execute(issuable)
......
# frozen_string_literal: true
module Issues module Issues
class BaseService < ::IssuableBaseService class BaseService < ::IssuableBaseService
def hook_data(issue, action, old_associations: {}) def hook_data(issue, action, old_associations: {})
......
# frozen_string_literal: true
module Issues module Issues
class BuildService < Issues::BaseService class BuildService < Issues::BaseService
include ResolveDiscussions include ResolveDiscussions
...@@ -45,14 +47,14 @@ module Issues ...@@ -45,14 +47,14 @@ module Issues
other_note_count = discussion.notes.size - 1 other_note_count = discussion.notes.size - 1
discussion_info = "- [ ] #{first_note_to_resolve.author.to_reference} #{action} a [discussion](#{note_url}): " discussion_info = ["- [ ] #{first_note_to_resolve.author.to_reference} #{action} a [discussion](#{note_url}): "]
discussion_info << " (+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0 discussion_info << "(+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0
note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note_to_resolve.note).call note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note_to_resolve.note).call
spaces = ' ' * 4 spaces = ' ' * 4
quote = note_without_block_quotes.lines.map { |line| "#{spaces}> #{line}" }.join quote = note_without_block_quotes.lines.map { |line| "#{spaces}> #{line}" }.join
[discussion_info, quote].join("\n\n") [discussion_info.join(' '), quote].join("\n\n")
end end
def issue_params def issue_params
......
# frozen_string_literal: true
module Issues module Issues
class CloseService < Issues::BaseService class CloseService < Issues::BaseService
# Closes the supplied issue if the current user is able to do so. # Closes the supplied issue if the current user is able to do so.
......
# frozen_string_literal: true
module Issues module Issues
class CreateService < Issues::BaseService class CreateService < Issues::BaseService
include SpamCheckService include SpamCheckService
......
# frozen_string_literal: true
module Issues module Issues
class DuplicateService < Issues::BaseService class DuplicateService < Issues::BaseService
def execute(duplicate_issue, canonical_issue) def execute(duplicate_issue, canonical_issue)
......
# frozen_string_literal: true
module Issues module Issues
class FetchReferencedMergeRequestsService < Issues::BaseService class FetchReferencedMergeRequestsService < Issues::BaseService
def execute(issue) def execute(issue)
......
# frozen_string_literal: true
module Issues module Issues
class MoveService < Issues::BaseService class MoveService < Issues::BaseService
prepend ::EE::Issues::MoveService prepend ::EE::Issues::MoveService
......
# frozen_string_literal: true
module Issues module Issues
class ReopenService < Issues::BaseService class ReopenService < Issues::BaseService
def execute(issue) def execute(issue)
......
# frozen_string_literal: true
module Issues module Issues
class UpdateService < Issues::BaseService class UpdateService < Issues::BaseService
include SpamCheckService include SpamCheckService
......
# frozen_string_literal: true
module Keys module Keys
class BaseService class BaseService
attr_accessor :user, :params attr_accessor :user, :params
......
# frozen_string_literal: true
module Keys module Keys
class CreateService < ::Keys::BaseService class CreateService < ::Keys::BaseService
prepend EE::Keys::CreateService prepend EE::Keys::CreateService
......
# frozen_string_literal: true
module Keys module Keys
class DestroyService < ::Keys::BaseService class DestroyService < ::Keys::BaseService
prepend EE::Keys::DestroyService prepend EE::Keys::DestroyService
......
# frozen_string_literal: true
module Keys module Keys
class LastUsedService class LastUsedService
TIMEOUT = 1.day.to_i TIMEOUT = 1.day.to_i
......
# frozen_string_literal: true
module Labels module Labels
class BaseService < ::BaseService class BaseService < ::BaseService
COLOR_NAME_TO_HEX = { COLOR_NAME_TO_HEX = {
......
# frozen_string_literal: true
module Labels module Labels
class CreateService < Labels::BaseService class CreateService < Labels::BaseService
def initialize(params = {}) def initialize(params = {})
......
# frozen_string_literal: true
module Labels module Labels
class FindOrCreateService class FindOrCreateService
def initialize(current_user, parent, params = {}) def initialize(current_user, parent, params = {})
......
# frozen_string_literal: true
module Labels module Labels
class PromoteService < BaseService class PromoteService < BaseService
BATCH_SIZE = 1000 BATCH_SIZE = 1000
......
# frozen_string_literal: true
# Labels::TransferService class # Labels::TransferService class
# #
# User for recreate the missing group labels at project level # User for recreate the missing group labels at project level
......
# frozen_string_literal: true
module Labels module Labels
class UpdateService < Labels::BaseService class UpdateService < Labels::BaseService
def initialize(params = {}) def initialize(params = {})
......
- link = link_to _("GitLab Runner section"), 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank' - link = link_to _("Install GitLab Runner"), 'https://docs.gitlab.com/runner/install/', target: '_blank'
.append-bottom-10 .append-bottom-10
%h4= _("Setup a #{type} Runner manually") %h4= _("Setup a #{type} Runner manually")
%ol %ol
%li %li
= _("Install a Runner compatible with GitLab CI") = link.html_safe
= (_("(check out the %{link} for information on how to install it).") % { link: link }).html_safe
%li %li
= _("Specify the following URL during the Runner setup:") = _("Specify the following URL during the Runner setup:")
%code#coordinator_address= root_url(only_path: false) %code#coordinator_address= root_url(only_path: false)
= clipboard_button(target: '#coordinator_address', title: _("Copy URL to clipboard"), class: "btn-transparent btn-clipboard")
%li %li
= _("Use the following registration token during setup:") = _("Use the following registration token during setup:")
%code#registration_token= registration_token %code#registration_token= registration_token
= clipboard_button(target: '#registration_token', title: _("Copy token to clipboard"), class: "btn-transparent btn-clipboard")
%li %li
= _("Start the Runner!") = _("Start the Runner!")
---
title: Add merge request header branch actions left margin
merge_request: 20643
author: George Tsiolis
type: changed
---
title: Enable frozen string in app/services/**/*.rb
merge_request: 20656
author: gfyoung
type: performance
---
title: Include full image URL in webhooks for uploaded images
merge_request: 18109
author: Satish Perala
type: changed
---
title: Fix symlink vulnerability in project import
merge_request:
author:
type: security
---
title: Update specific runners help URL
merge_request: 20213
author: George Tsiolis
type: other
...@@ -10,6 +10,13 @@ Starting from GitLab 8.5: ...@@ -10,6 +10,13 @@ Starting from GitLab 8.5:
Starting from GitLab 11.1, the logs of web hooks are automatically removed after Starting from GitLab 11.1, the logs of web hooks are automatically removed after
one month. one month.
>**Note**
Starting from GitLab 11.2:
- The `description` field for issues, merge requests, comments, and wiki pages
is rewritten so that simple Markdown image references (like
`![](/uploads/...)`) have their target URL changed to an absolute URL. See
[image URL rewriting](#image-url-rewriting) for more details.
Project webhooks allow you to trigger a URL if for example new code is pushed or Project webhooks allow you to trigger a URL if for example new code is pushed or
a new issue is created. You can configure webhooks to listen for specific events a new issue is created. You can configure webhooks to listen for specific events
like pushes, issues or merge requests. GitLab will send a POST request with data like pushes, issues or merge requests. GitLab will send a POST request with data
...@@ -1150,6 +1157,27 @@ X-Gitlab-Event: Build Hook ...@@ -1150,6 +1157,27 @@ X-Gitlab-Event: Build Hook
} }
``` ```
## Image URL rewriting
From GitLab 11.2, simple image references are rewritten to use an absolute URL
in webhooks. So if an image, merge request, comment, or wiki page has this in
its description:
```markdown
![image](/uploads/$sha/image.png)
```
It will appear in the webhook body as the below (assuming that GitLab is
installed at gitlab.example.com):
```markdown
![image](https://gitlab.example.com/uploads/$sha/image.png)
```
This will not rewrite URLs that already are pointing to HTTP, HTTPS, or
protocol-relative URLs. It will also not rewrite image URLs using advanced
Markdown features, like link labels.
## Testing webhooks ## Testing webhooks
You can trigger the webhook manually. Sample data from the project will be used.Sample data will take from the project. You can trigger the webhook manually. Sample data from the project will be used.Sample data will take from the project.
......
...@@ -2,27 +2,7 @@ module Banzai ...@@ -2,27 +2,7 @@ module Banzai
module Filter module Filter
class BlockquoteFenceFilter < HTML::Pipeline::TextFilter class BlockquoteFenceFilter < HTML::Pipeline::TextFilter
REGEX = %r{ REGEX = %r{
(?<code> #{::Gitlab::Regex.markdown_code_or_html_blocks}
# Code blocks:
# ```
# Anything, including `>>>` blocks which are ignored by this filter
# ```
^```
.+?
\n```\ *$
)
|
(?<html>
# HTML block:
# <tag>
# Anything, including `>>>` blocks which are ignored by this filter
# </tag>
^<[^>]+?>\ *\n
.+?
\n<\/[^>]+?>\ *$
)
| |
(?: (?:
# Blockquote: # Blockquote:
......
module Gitlab
module HookData
class BaseBuilder
attr_accessor :object
MARKDOWN_SIMPLE_IMAGE = %r{
#{::Gitlab::Regex.markdown_code_or_html_blocks}
|
(?<image>
!
\[(?<title>[^\n]*?)\]
\((?<url>(?!(https?://|//))[^\n]+?)\)
)
}mx.freeze
def initialize(object)
@object = object
end
private
def absolute_image_urls(markdown_text)
return markdown_text unless markdown_text.present?
markdown_text.gsub(MARKDOWN_SIMPLE_IMAGE) do
if $~[:image]
url = $~[:url]
url = "/#{url}" unless url.start_with?('/')
"![#{$~[:title]}](#{Gitlab.config.gitlab.url}#{url})"
else
$~[0]
end
end
end
end
end
end
module Gitlab module Gitlab
module HookData module HookData
class IssuableBuilder class IssuableBuilder < BaseBuilder
CHANGES_KEYS = %i[previous current].freeze CHANGES_KEYS = %i[previous current].freeze
attr_accessor :issuable alias_method :issuable, :object
def initialize(issuable)
@issuable = issuable
end
def build(user: nil, changes: {}) def build(user: nil, changes: {})
hook_data = { hook_data = {
......
module Gitlab module Gitlab
module HookData module HookData
class IssueBuilder class IssueBuilder < BaseBuilder
SAFE_HOOK_ATTRIBUTES = %i[ SAFE_HOOK_ATTRIBUTES = %i[
assignee_id assignee_id
author_id author_id
...@@ -30,14 +30,11 @@ module Gitlab ...@@ -30,14 +30,11 @@ module Gitlab
total_time_spent total_time_spent
].freeze ].freeze
attr_accessor :issue alias_method :issue, :object
def initialize(issue)
@issue = issue
end
def build def build
attrs = { attrs = {
description: absolute_image_urls(issue.description),
url: Gitlab::UrlBuilder.build(issue), url: Gitlab::UrlBuilder.build(issue),
total_time_spent: issue.total_time_spent, total_time_spent: issue.total_time_spent,
human_total_time_spent: issue.human_total_time_spent, human_total_time_spent: issue.human_total_time_spent,
......
module Gitlab module Gitlab
module HookData module HookData
class MergeRequestBuilder class MergeRequestBuilder < BaseBuilder
SAFE_HOOK_ATTRIBUTES = %i[ SAFE_HOOK_ATTRIBUTES = %i[
assignee_id assignee_id
author_id author_id
...@@ -35,14 +35,11 @@ module Gitlab ...@@ -35,14 +35,11 @@ module Gitlab
total_time_spent total_time_spent
].freeze ].freeze
attr_accessor :merge_request alias_method :merge_request, :object
def initialize(merge_request)
@merge_request = merge_request
end
def build def build
attrs = { attrs = {
description: absolute_image_urls(merge_request.description),
url: Gitlab::UrlBuilder.build(merge_request), url: Gitlab::UrlBuilder.build(merge_request),
source: merge_request.source_project.try(:hook_attrs), source: merge_request.source_project.try(:hook_attrs),
target: merge_request.target_project.hook_attrs, target: merge_request.target_project.hook_attrs,
......
module Gitlab
module HookData
class NoteBuilder < BaseBuilder
SAFE_HOOK_ATTRIBUTES = %i[
attachment
author_id
change_position
commit_id
created_at
discussion_id
id
line_code
note
noteable_id
noteable_type
original_position
position
project_id
resolved_at
resolved_by_id
resolved_by_push
st_diff
system
type
updated_at
updated_by_id
].freeze
alias_method :note, :object
def build
note
.attributes
.with_indifferent_access
.slice(*SAFE_HOOK_ATTRIBUTES)
.merge(
description: absolute_image_urls(note.note),
url: Gitlab::UrlBuilder.build(note)
)
end
end
end
end
module Gitlab
module HookData
class WikiPageBuilder < BaseBuilder
alias_method :wiki_page, :object
def build
wiki_page
.attributes
.merge(
'content' => absolute_image_urls(wiki_page.content)
)
end
end
end
end
...@@ -4,6 +4,7 @@ module Gitlab ...@@ -4,6 +4,7 @@ module Gitlab
include Gitlab::ImportExport::CommandLineUtil include Gitlab::ImportExport::CommandLineUtil
MAX_RETRIES = 8 MAX_RETRIES = 8
IGNORED_FILENAMES = %w(. ..).freeze
def self.import(*args) def self.import(*args)
new(*args).import new(*args).import
...@@ -59,7 +60,7 @@ module Gitlab ...@@ -59,7 +60,7 @@ module Gitlab
end end
def extracted_files def extracted_files
Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ %r{.*/\.{1,2}$} } Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| IGNORED_FILENAMES.include?(File.basename(f)) }
end end
end end
end end
......
...@@ -74,5 +74,31 @@ module Gitlab ...@@ -74,5 +74,31 @@ module Gitlab
def build_trace_section_regex def build_trace_section_regex
@build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([a-zA-Z0-9_.-]+)\r\033\[0K/.freeze @build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([a-zA-Z0-9_.-]+)\r\033\[0K/.freeze
end end
def markdown_code_or_html_blocks
@markdown_code_or_html_blocks ||= %r{
(?<code>
# Code blocks:
# ```
# Anything, including `>>>` blocks which are ignored by this filter
# ```
^```
.+?
\n```\ *$
)
|
(?<html>
# HTML block:
# <tag>
# Anything, including `>>>` blocks which are ignored by this filter
# </tag>
^<[^>]+?>\ *\n
.+?
\n<\/[^>]+?>\ *$
)
}mx
end
end end
end end
...@@ -8,8 +8,13 @@ msgid "" ...@@ -8,8 +8,13 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
<<<<<<< HEAD
"POT-Creation-Date: 2018-07-17 07:12+0000\n" "POT-Creation-Date: 2018-07-17 07:12+0000\n"
"PO-Revision-Date: 2018-07-17 07:12+0000\n" "PO-Revision-Date: 2018-07-17 07:12+0000\n"
=======
"POT-Creation-Date: 2018-07-10 16:02-0700\n"
"PO-Revision-Date: 2018-07-10 16:02-0700\n"
>>>>>>> upstream/master
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -162,9 +167,6 @@ msgstr[1] "" ...@@ -162,9 +167,6 @@ msgstr[1] ""
msgid "%{unstaged} unstaged and %{staged} staged changes" msgid "%{unstaged} unstaged and %{staged} staged changes"
msgstr "" msgstr ""
msgid "(check out the %{link} for information on how to install it)."
msgstr ""
msgid "+ %{moreCount} more" msgid "+ %{moreCount} more"
msgstr "" msgstr ""
...@@ -2062,6 +2064,9 @@ msgstr "" ...@@ -2062,6 +2064,9 @@ msgstr ""
msgid "Copy to clipboard" msgid "Copy to clipboard"
msgstr "" msgstr ""
msgid "Copy token to clipboard"
msgstr ""
msgid "Create" msgid "Create"
msgstr "" msgstr ""
...@@ -3188,9 +3193,6 @@ msgstr "" ...@@ -3188,9 +3193,6 @@ msgstr ""
msgid "GitLab Import" msgid "GitLab Import"
msgstr "" msgstr ""
msgid "GitLab Runner section"
msgstr ""
msgid "GitLab User" msgid "GitLab User"
msgstr "" msgstr ""
...@@ -3580,10 +3582,10 @@ msgstr "" ...@@ -3580,10 +3582,10 @@ msgstr ""
msgid "Inline" msgid "Inline"
msgstr "" msgstr ""
msgid "Install Runner on Kubernetes" msgid "Install GitLab Runner"
msgstr "" msgstr ""
msgid "Install a Runner compatible with GitLab CI" msgid "Install Runner on Kubernetes"
msgstr "" msgstr ""
msgid "Instance" msgid "Instance"
......
...@@ -24,7 +24,7 @@ describe('diff_file_header', () => { ...@@ -24,7 +24,7 @@ describe('diff_file_header', () => {
const diffFile = convertObjectPropsToCamelCase(diffDiscussionMock.diff_file, { deep: true }); const diffFile = convertObjectPropsToCamelCase(diffDiscussionMock.diff_file, { deep: true });
props = { props = {
diffFile, diffFile,
currentUser: {}, canCurrentUserFork: false,
}; };
}); });
......
...@@ -11,7 +11,7 @@ describe('DiffFile', () => { ...@@ -11,7 +11,7 @@ describe('DiffFile', () => {
beforeEach(() => { beforeEach(() => {
vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, { vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, {
file: getDiffFileMock(), file: getDiffFileMock(),
currentUser: {}, canCurrentUserFork: false,
}).$mount(); }).$mount();
}); });
......
...@@ -15,7 +15,7 @@ describe('DiffLineNoteForm', () => { ...@@ -15,7 +15,7 @@ describe('DiffLineNoteForm', () => {
diffLines = diffFile.highlightedDiffLines; diffLines = diffFile.highlightedDiffLines;
component = createComponentWithStore(Vue.extend(DiffLineNoteForm), store, { component = createComponentWithStore(Vue.extend(DiffLineNoteForm), store, {
diffFile, diffFileHash: diffFile.fileHash,
diffLines, diffLines,
line: diffLines[0], line: diffLines[0],
noteTargetLine: diffLines[0], noteTargetLine: diffLines[0],
......
...@@ -184,4 +184,23 @@ describe('Diffs Module Getters', () => { ...@@ -184,4 +184,23 @@ describe('Diffs Module Getters', () => {
).toEqual(0); ).toEqual(0);
}); });
}); });
describe('getDiffFileByHash', () => {
it('returns file by hash', () => {
const fileA = {
fileHash: '123',
};
const fileB = {
fileHash: '456',
};
localState.diffFiles = [fileA, fileB];
expect(getters.getDiffFileByHash(localState)('456')).toEqual(fileB);
});
it('returns null if no matching file is found', () => {
localState.diffFiles = [];
expect(getters.getDiffFileByHash(localState)('123')).toBeUndefined();
});
});
}); });
...@@ -6561,6 +6561,9 @@ export const environmentData = [ ...@@ -6561,6 +6561,9 @@ export const environmentData = [
folder_path: '/root/hello-prometheus/environments/folders/production', folder_path: '/root/hello-prometheus/environments/folders/production',
created_at: '2018-06-29T16:53:38.301Z', created_at: '2018-06-29T16:53:38.301Z',
updated_at: '2018-06-29T16:57:09.825Z', updated_at: '2018-06-29T16:57:09.825Z',
last_deployment: {
id: 127,
},
}, },
}, },
{ {
...@@ -6580,6 +6583,20 @@ export const environmentData = [ ...@@ -6580,6 +6583,20 @@ export const environmentData = [
folder_path: '/root/hello-prometheus/environments/folders/review', folder_path: '/root/hello-prometheus/environments/folders/review',
created_at: '2018-07-03T18:39:41.702Z', created_at: '2018-07-03T18:39:41.702Z',
updated_at: '2018-07-03T18:44:54.010Z', updated_at: '2018-07-03T18:44:54.010Z',
last_deployment: {
id: 128,
},
},
},
{
name: 'no-deployment',
size: 1,
latest: {
id: 36,
name: 'no-deployment/noop-branch',
state: 'available',
created_at: '2018-07-04T18:39:41.702Z',
updated_at: '2018-07-04T18:44:54.010Z',
}, },
}, },
]; ];
import MonitoringStore from '~/monitoring/stores/monitoring_store'; import MonitoringStore from '~/monitoring/stores/monitoring_store';
import MonitoringMock, { deploymentData } from './mock_data'; import MonitoringMock, { deploymentData, environmentData } from './mock_data';
describe('MonitoringStore', function () { describe('MonitoringStore', function () {
this.store = new MonitoringStore(); this.store = new MonitoringStore();
...@@ -21,4 +21,9 @@ describe('MonitoringStore', function () { ...@@ -21,4 +21,9 @@ describe('MonitoringStore', function () {
expect(this.store.deploymentData.length).toEqual(3); expect(this.store.deploymentData.length).toEqual(3);
expect(typeof this.store.deploymentData[0]).toEqual('object'); expect(typeof this.store.deploymentData[0]).toEqual('object');
}); });
it('only stores environment data that contains deployments', () => {
this.store.storeEnvironmentsData(environmentData);
expect(this.store.environmentsData.length).toEqual(2);
});
}); });
require 'spec_helper'
describe Gitlab::HookData::BaseBuilder do
describe '#absolute_image_urls' do
let(:subclass) do
Class.new(described_class) do
public :absolute_image_urls
end
end
subject { subclass.new(nil) }
using RSpec::Parameterized::TableSyntax
where do
{
'relative image URL' => {
input: '![an image](foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)"
},
'HTTP URL' => {
input: '![an image](http://example.com/foo.png)',
output: '![an image](http://example.com/foo.png)'
},
'HTTPS URL' => {
input: '![an image](https://example.com/foo.png)',
output: '![an image](https://example.com/foo.png)'
},
'protocol-relative URL' => {
input: '![an image](//example.com/foo.png)',
output: '![an image](//example.com/foo.png)'
},
'URL reference by title' => {
input: "![foo]\n\n[foo]: foo.png",
output: "![foo]\n\n[foo]: foo.png"
},
'URL reference by label' => {
input: "![][foo]\n\n[foo]: foo.png",
output: "![][foo]\n\n[foo]: foo.png"
},
'in Markdown inline code block' => {
input: '`![an image](foo.png)`',
output: "`![an image](#{Gitlab.config.gitlab.url}/foo.png)`"
},
'in HTML tag on the same line' => {
input: '<p>![an image](foo.png)</p>',
output: "<p>![an image](#{Gitlab.config.gitlab.url}/foo.png)</p>"
},
'in Markdown multi-line code block' => {
input: "```\n![an image](foo.png)\n```",
output: "```\n![an image](foo.png)\n```"
},
'in HTML tag on different lines' => {
input: "<p>\n![an image](foo.png)\n</p>",
output: "<p>\n![an image](foo.png)\n</p>"
}
}
end
with_them do
it { expect(subject.absolute_image_urls(input)).to eq(output) }
end
end
end
...@@ -40,5 +40,14 @@ describe Gitlab::HookData::IssueBuilder do ...@@ -40,5 +40,14 @@ describe Gitlab::HookData::IssueBuilder do
expect(data).to include(:human_total_time_spent) expect(data).to include(:human_total_time_spent)
expect(data).to include(:assignee_ids) expect(data).to include(:assignee_ids)
end end
context 'when the issue has an image in the description' do
let(:issue_with_description) { create(:issue, description: 'test![Issue_Image](/uploads/abc/Issue_Image.png)') }
let(:builder) { described_class.new(issue_with_description) }
it 'sets the image to use an absolute URL' do
expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)")
end
end
end end
end end
...@@ -56,5 +56,14 @@ describe Gitlab::HookData::MergeRequestBuilder do ...@@ -56,5 +56,14 @@ describe Gitlab::HookData::MergeRequestBuilder do
expect(data).to include(:human_time_estimate) expect(data).to include(:human_time_estimate)
expect(data).to include(:human_total_time_spent) expect(data).to include(:human_total_time_spent)
end end
context 'when the MR has an image in the description' do
let(:mr_with_description) { create(:merge_request, description: 'test![Issue_Image](/uploads/abc/Issue_Image.png)') }
let(:builder) { described_class.new(mr_with_description) }
it 'sets the image to use an absolute URL' do
expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)")
end
end
end end
end end
...@@ -7,6 +7,7 @@ describe Gitlab::ImportExport::FileImporter do ...@@ -7,6 +7,7 @@ describe Gitlab::ImportExport::FileImporter do
let(:symlink_file) { "#{shared.export_path}/invalid.json" } let(:symlink_file) { "#{shared.export_path}/invalid.json" }
let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" } let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" } let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" }
before do before do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
...@@ -34,6 +35,10 @@ describe Gitlab::ImportExport::FileImporter do ...@@ -34,6 +35,10 @@ describe Gitlab::ImportExport::FileImporter do
expect(File.exist?(hidden_symlink_file)).to be false expect(File.exist?(hidden_symlink_file)).to be false
end end
it 'removes evil symlinks in root folder' do
expect(File.exist?(evil_symlink_file)).to be false
end
it 'removes symlinks in subfolders' do it 'removes symlinks in subfolders' do
expect(File.exist?(subfolder_symlink_file)).to be false expect(File.exist?(subfolder_symlink_file)).to be false
end end
...@@ -75,5 +80,7 @@ describe Gitlab::ImportExport::FileImporter do ...@@ -75,5 +80,7 @@ describe Gitlab::ImportExport::FileImporter do
FileUtils.touch(valid_file) FileUtils.touch(valid_file)
FileUtils.ln_s(valid_file, symlink_file) FileUtils.ln_s(valid_file, symlink_file)
FileUtils.ln_s(valid_file, subfolder_symlink_file) FileUtils.ln_s(valid_file, subfolder_symlink_file)
FileUtils.ln_s(valid_file, hidden_symlink_file)
FileUtils.ln_s(valid_file, evil_symlink_file)
end end
end end
...@@ -554,6 +554,16 @@ describe WikiPage do ...@@ -554,6 +554,16 @@ describe WikiPage do
end end
end end
describe '#hook_attrs' do
it 'adds absolute urls for images in the content' do
create_page("test page", "test![WikiPage_Image](/uploads/abc/WikiPage_Image.png)")
page = wiki.wiki.page(title: "test page")
wiki_page = described_class.new(wiki, page, true)
expect(wiki_page.hook_attrs['content']).to eq("test![WikiPage_Image](#{Settings.gitlab.url}/uploads/abc/WikiPage_Image.png)")
end
end
private private
def remove_temp_repo(path) def remove_temp_repo(path)
......
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