Merge branch 'master' into backstage/gb/build-stages-catch-up-migration
* master: (26 commits)
This commit is contained in:
commit
14a9c951cc
|
@ -39,7 +39,7 @@ export default class VariableList {
|
|||
},
|
||||
protected: {
|
||||
selector: '.js-ci-variable-input-protected',
|
||||
default: 'true',
|
||||
default: 'false',
|
||||
},
|
||||
environment_scope: {
|
||||
// We can't use a `.js-` class here because
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
|
||||
import 'vendor/jquery.waitforimages';
|
||||
|
||||
// Width where images must fits in, for 2-up this gets divided by 2
|
||||
const availWidth = 900;
|
||||
|
|
|
@ -6,5 +6,5 @@ import 'vendor/jquery.endless-scroll';
|
|||
import 'vendor/jquery.caret';
|
||||
import 'vendor/jquery.atwho';
|
||||
import 'vendor/jquery.scrollTo';
|
||||
import 'vendor/jquery.waitforimages';
|
||||
import 'jquery.waitforimages';
|
||||
import 'select2/select2';
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script>
|
||||
import Timeago from 'timeago.js';
|
||||
import _ from 'underscore';
|
||||
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import { humanize } from '../../lib/utils/text_utility';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import { humanize } from '~/lib/utils/text_utility';
|
||||
import ActionsComponent from './environment_actions.vue';
|
||||
import ExternalUrlComponent from './environment_external_url.vue';
|
||||
import StopComponent from './environment_stop.vue';
|
||||
|
@ -21,14 +22,18 @@
|
|||
|
||||
export default {
|
||||
components: {
|
||||
userAvatarLink,
|
||||
'commit-component': CommitComponent,
|
||||
'actions-component': ActionsComponent,
|
||||
'external-url-component': ExternalUrlComponent,
|
||||
'stop-component': StopComponent,
|
||||
'rollback-component': RollbackComponent,
|
||||
'terminal-button-component': TerminalButtonComponent,
|
||||
'monitoring-button-component': MonitoringButtonComponent,
|
||||
UserAvatarLink,
|
||||
CommitComponent,
|
||||
ActionsComponent,
|
||||
ExternalUrlComponent,
|
||||
StopComponent,
|
||||
RollbackComponent,
|
||||
TerminalButtonComponent,
|
||||
MonitoringButtonComponent,
|
||||
},
|
||||
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -443,7 +448,11 @@
|
|||
v-if="!model.isFolder"
|
||||
class="environment-name flex-truncate-parent table-mobile-content"
|
||||
:href="environmentPath">
|
||||
<span class="flex-truncate-child">{{ model.name }}</span>
|
||||
<span
|
||||
class="flex-truncate-child"
|
||||
v-tooltip
|
||||
:title="model.name"
|
||||
>{{ model.name }}</span>
|
||||
</a>
|
||||
<span
|
||||
v-else
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { __ } from './locale';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import flash from './flash';
|
||||
|
||||
class ImporterStatus {
|
||||
constructor(jobsUrl, importUrl) {
|
||||
this.jobsUrl = jobsUrl;
|
||||
|
@ -9,29 +13,7 @@ class ImporterStatus {
|
|||
initStatusPage() {
|
||||
$('.js-add-to-import')
|
||||
.off('click')
|
||||
.on('click', (event) => {
|
||||
const $btn = $(event.currentTarget);
|
||||
const $tr = $btn.closest('tr');
|
||||
const $targetField = $tr.find('.import-target');
|
||||
const $namespaceInput = $targetField.find('.js-select-namespace option:selected');
|
||||
const id = $tr.attr('id').replace('repo_', '');
|
||||
let targetNamespace;
|
||||
let newName;
|
||||
if ($namespaceInput.length > 0) {
|
||||
targetNamespace = $namespaceInput[0].innerHTML;
|
||||
newName = $targetField.find('#path').prop('value');
|
||||
$targetField.empty().append(`${targetNamespace}/${newName}`);
|
||||
}
|
||||
$btn.disable().addClass('is-loading');
|
||||
|
||||
return $.post(this.importUrl, {
|
||||
repo_id: id,
|
||||
target_namespace: targetNamespace,
|
||||
new_name: newName,
|
||||
}, {
|
||||
dataType: 'script',
|
||||
});
|
||||
});
|
||||
.on('click', this.addToImport.bind(this));
|
||||
|
||||
$('.js-import-all')
|
||||
.off('click')
|
||||
|
@ -44,6 +26,39 @@ class ImporterStatus {
|
|||
});
|
||||
}
|
||||
|
||||
addToImport(event) {
|
||||
const $btn = $(event.currentTarget);
|
||||
const $tr = $btn.closest('tr');
|
||||
const $targetField = $tr.find('.import-target');
|
||||
const $namespaceInput = $targetField.find('.js-select-namespace option:selected');
|
||||
const id = $tr.attr('id').replace('repo_', '');
|
||||
let targetNamespace;
|
||||
let newName;
|
||||
if ($namespaceInput.length > 0) {
|
||||
targetNamespace = $namespaceInput[0].innerHTML;
|
||||
newName = $targetField.find('#path').prop('value');
|
||||
$targetField.empty().append(`${targetNamespace}/${newName}`);
|
||||
}
|
||||
$btn.disable().addClass('is-loading');
|
||||
|
||||
return axios.post(this.importUrl, {
|
||||
repo_id: id,
|
||||
target_namespace: targetNamespace,
|
||||
new_name: newName,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const job = $(`tr#repo_${id}`);
|
||||
job.attr('id', `project_${data.id}`);
|
||||
|
||||
job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
|
||||
$('table.import-jobs tbody').prepend(job);
|
||||
|
||||
job.addClass('active');
|
||||
job.find('.import-actions').html('<i class="fa fa-spinner fa-spin" aria-label="importing"></i> started');
|
||||
})
|
||||
.catch(() => flash(__('An error occurred while importing project')));
|
||||
}
|
||||
|
||||
setAutoUpdate() {
|
||||
return setInterval(() => $.get(this.jobsUrl, data => $.each(data, (i, job) => {
|
||||
const jobItem = $(`#project_${job.id}`);
|
||||
|
@ -71,7 +86,7 @@ class ImporterStatus {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
export default function initImporterStatus() {
|
||||
function initImporterStatus() {
|
||||
const importerStatus = document.querySelector('.js-importer-status');
|
||||
|
||||
if (importerStatus) {
|
||||
|
@ -79,3 +94,8 @@ export default function initImporterStatus() {
|
|||
return new ImporterStatus(data.jobsImportPath, data.importPath);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
initImporterStatus as default,
|
||||
ImporterStatus,
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
|
||||
import 'vendor/jquery.waitforimages';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { addDelimiter } from './lib/utils/text_utility';
|
||||
import flash from './flash';
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
|
||||
|
||||
import 'vendor/jquery.waitforimages';
|
||||
import { __ } from '~/locale';
|
||||
import TaskList from './task_list';
|
||||
import MergeRequestTabs from './merge_request_tabs';
|
||||
|
|
|
@ -30,6 +30,9 @@ export default function renderMermaid($els) {
|
|||
$els.each((i, el) => {
|
||||
const source = el.textContent;
|
||||
|
||||
// Remove any extra spans added by the backend syntax highlighting.
|
||||
Object.assign(el, { textContent: source });
|
||||
|
||||
mermaid.init(undefined, el, (id) => {
|
||||
const svg = document.getElementById(id);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'underscore';
|
|||
|
||||
import '~/smart_interval';
|
||||
|
||||
import timeTracker from './time_tracker';
|
||||
import IssuableTimeTracker from './time_tracker.vue';
|
||||
|
||||
import Store from '../../stores/sidebar_store';
|
||||
import Mediator from '../../sidebar_mediator';
|
||||
|
@ -16,7 +16,7 @@ export default {
|
|||
};
|
||||
},
|
||||
components: {
|
||||
'issuable-time-tracker': timeTracker,
|
||||
IssuableTimeTracker,
|
||||
},
|
||||
methods: {
|
||||
listenForQuickActions() {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<script>
|
||||
import timeTrackingHelpState from './help_state';
|
||||
import timeTrackingCollapsedState from './collapsed_state';
|
||||
import timeTrackingSpentOnlyPane from './spent_only_pane';
|
||||
|
@ -8,7 +9,15 @@ import timeTrackingComparisonPane from './comparison_pane';
|
|||
import eventHub from '../../event_hub';
|
||||
|
||||
export default {
|
||||
name: 'issuable-time-tracker',
|
||||
name: 'IssuableTimeTracker',
|
||||
components: {
|
||||
'time-tracking-collapsed-state': timeTrackingCollapsedState,
|
||||
'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
|
||||
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
|
||||
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
|
||||
'time-tracking-comparison-pane': timeTrackingComparisonPane,
|
||||
'time-tracking-help-state': timeTrackingHelpState,
|
||||
},
|
||||
props: {
|
||||
time_estimate: {
|
||||
type: Number,
|
||||
|
@ -38,14 +47,6 @@ export default {
|
|||
showHelp: false,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
'time-tracking-collapsed-state': timeTrackingCollapsedState,
|
||||
'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
|
||||
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
|
||||
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
|
||||
'time-tracking-comparison-pane': timeTrackingComparisonPane,
|
||||
'time-tracking-help-state': timeTrackingHelpState,
|
||||
},
|
||||
computed: {
|
||||
timeSpent() {
|
||||
return this.time_spent;
|
||||
|
@ -81,6 +82,9 @@ export default {
|
|||
return !!this.showHelp;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
eventHub.$on('timeTracker:updateData', this.update);
|
||||
},
|
||||
methods: {
|
||||
toggleHelpState(show) {
|
||||
this.showHelp = show;
|
||||
|
@ -92,72 +96,73 @@ export default {
|
|||
this.human_time_spent = data.human_time_spent;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
eventHub.$on('timeTracker:updateData', this.update);
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="time_tracker time-tracking-component-wrap"
|
||||
v-cloak
|
||||
>
|
||||
<time-tracking-collapsed-state
|
||||
:show-comparison-state="showComparisonState"
|
||||
:show-no-time-tracking-state="showNoTimeTrackingState"
|
||||
:show-help-state="showHelpState"
|
||||
:show-spent-only-state="showSpentOnlyState"
|
||||
:show-estimate-only-state="showEstimateOnlyState"
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="time_tracker time-tracking-component-wrap"
|
||||
v-cloak
|
||||
>
|
||||
<time-tracking-collapsed-state
|
||||
:show-comparison-state="showComparisonState"
|
||||
:show-no-time-tracking-state="showNoTimeTrackingState"
|
||||
:show-help-state="showHelpState"
|
||||
:show-spent-only-state="showSpentOnlyState"
|
||||
:show-estimate-only-state="showEstimateOnlyState"
|
||||
:time-spent-human-readable="timeSpentHumanReadable"
|
||||
:time-estimate-human-readable="timeEstimateHumanReadable"
|
||||
/>
|
||||
<div class="title hide-collapsed">
|
||||
{{ __('Time tracking') }}
|
||||
<div
|
||||
class="help-button pull-right"
|
||||
v-if="!showHelpState"
|
||||
@click="toggleHelpState(true)"
|
||||
>
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</div>
|
||||
<div
|
||||
class="close-help-button pull-right"
|
||||
v-if="showHelpState"
|
||||
@click="toggleHelpState(false)"
|
||||
>
|
||||
<i
|
||||
class="fa fa-close"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-tracking-content hide-collapsed">
|
||||
<time-tracking-estimate-only-pane
|
||||
v-if="showEstimateOnlyState"
|
||||
:time-estimate-human-readable="timeEstimateHumanReadable"
|
||||
/>
|
||||
<time-tracking-spent-only-pane
|
||||
v-if="showSpentOnlyState"
|
||||
:time-spent-human-readable="timeSpentHumanReadable"
|
||||
/>
|
||||
<time-tracking-no-tracking-pane
|
||||
v-if="showNoTimeTrackingState"
|
||||
/>
|
||||
<time-tracking-comparison-pane
|
||||
v-if="showComparisonState"
|
||||
:time-estimate="timeEstimate"
|
||||
:time-spent="timeSpent"
|
||||
:time-spent-human-readable="timeSpentHumanReadable"
|
||||
:time-estimate-human-readable="timeEstimateHumanReadable"
|
||||
/>
|
||||
<div class="title hide-collapsed">
|
||||
{{ __('Time tracking') }}
|
||||
<div
|
||||
class="help-button pull-right"
|
||||
v-if="!showHelpState"
|
||||
@click="toggleHelpState(true)"
|
||||
>
|
||||
<i
|
||||
class="fa fa-question-circle"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="close-help-button pull-right"
|
||||
<transition name="help-state-toggle">
|
||||
<time-tracking-help-state
|
||||
v-if="showHelpState"
|
||||
@click="toggleHelpState(false)"
|
||||
>
|
||||
<i
|
||||
class="fa fa-close"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-tracking-content hide-collapsed">
|
||||
<time-tracking-estimate-only-pane
|
||||
v-if="showEstimateOnlyState"
|
||||
:time-estimate-human-readable="timeEstimateHumanReadable"
|
||||
:root-path="rootPath"
|
||||
/>
|
||||
<time-tracking-spent-only-pane
|
||||
v-if="showSpentOnlyState"
|
||||
:time-spent-human-readable="timeSpentHumanReadable"
|
||||
/>
|
||||
<time-tracking-no-tracking-pane
|
||||
v-if="showNoTimeTrackingState"
|
||||
/>
|
||||
<time-tracking-comparison-pane
|
||||
v-if="showComparisonState"
|
||||
:time-estimate="timeEstimate"
|
||||
:time-spent="timeSpent"
|
||||
:time-spent-human-readable="timeSpentHumanReadable"
|
||||
:time-estimate-human-readable="timeEstimateHumanReadable"
|
||||
/>
|
||||
<transition name="help-state-toggle">
|
||||
<time-tracking-help-state
|
||||
v-if="showHelpState"
|
||||
:rootPath="rootPath"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
</div>
|
||||
</template>
|
|
@ -1,43 +0,0 @@
|
|||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||
import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetMissingBranch',
|
||||
props: {
|
||||
mr: { type: Object, required: true },
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
'mr-widget-merge-help': mrWidgetMergeHelp,
|
||||
statusIcon,
|
||||
},
|
||||
computed: {
|
||||
missingBranchName() {
|
||||
return this.mr.sourceBranchRemoved ? 'source' : 'target';
|
||||
},
|
||||
message() {
|
||||
return `If the ${this.missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line`;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon status="warning" :show-disabled-button="true" />
|
||||
<div class="media-body space-children">
|
||||
<span class="bold js-branch-text">
|
||||
<span class="capitalize">
|
||||
{{missingBranchName}}
|
||||
</span> branch does not exist.
|
||||
Please restore it or use a different {{missingBranchName}} branch
|
||||
<i
|
||||
v-tooltip
|
||||
class="fa fa-question-circle"
|
||||
:title="message"
|
||||
:aria-label="message"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
<script>
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import statusIcon from '../mr_widget_status_icon.vue';
|
||||
import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetMissingBranch',
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
mrWidgetMergeHelp,
|
||||
statusIcon,
|
||||
},
|
||||
props: {
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
missingBranchName() {
|
||||
return this.mr.sourceBranchRemoved ? 'source' : 'target';
|
||||
},
|
||||
missingBranchNameMessage() {
|
||||
return sprintf(s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), {
|
||||
missingBranchName: this.missingBranchName,
|
||||
});
|
||||
},
|
||||
message() {
|
||||
return sprintf(s__('mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line'), {
|
||||
missingBranchName: this.missingBranchName,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="mr-widget-body media">
|
||||
<status-icon
|
||||
status="warning"
|
||||
:show-disabled-button="true"
|
||||
/>
|
||||
|
||||
<div class="media-body space-children">
|
||||
<span class="bold js-branch-text">
|
||||
<span class="capitalize">
|
||||
{{ missingBranchName }}
|
||||
</span> {{ s__("mrWidget|branch does not exist.") }}
|
||||
{{ missingBranchNameMessage }}
|
||||
<i
|
||||
v-tooltip
|
||||
class="fa fa-question-circle"
|
||||
:title="message"
|
||||
:aria-label="message"
|
||||
>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -24,7 +24,7 @@ export { default as WipState } from './components/states/mr_widget_wip';
|
|||
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
|
||||
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
|
||||
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
|
||||
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
|
||||
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
|
||||
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
|
||||
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
|
||||
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
inputId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
confirmationKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
confirmationValue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
shouldEscapeConfirmationValue: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
inputLabel() {
|
||||
let value = this.confirmationValue;
|
||||
if (this.shouldEscapeConfirmationValue) {
|
||||
value = _.escape(value);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
__('Type %{value} to confirm:'),
|
||||
{ value: `<code>${value}</code>` },
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasCorrectValue() {
|
||||
return this.$refs.enteredValue.value === this.confirmationValue;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
v-html="inputLabel"
|
||||
:for="inputId"
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
:id="inputId"
|
||||
:name="confirmationKey"
|
||||
type="text"
|
||||
ref="enteredValue"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -21,7 +21,7 @@
|
|||
@import "framework/flash";
|
||||
@import "framework/forms";
|
||||
@import "framework/gfm";
|
||||
@import "framework/gitlab-theme";
|
||||
@import "framework/gitlab_theme";
|
||||
@import "framework/header";
|
||||
@import "framework/highlight";
|
||||
@import "framework/issue_box";
|
||||
|
@ -35,10 +35,10 @@
|
|||
@import "framework/pagination";
|
||||
@import "framework/panels";
|
||||
@import "framework/popup";
|
||||
@import "framework/secondary-navigation-elements";
|
||||
@import "framework/secondary_navigation_elements";
|
||||
@import "framework/selects";
|
||||
@import "framework/sidebar";
|
||||
@import "framework/contextual-sidebar";
|
||||
@import "framework/contextual_sidebar";
|
||||
@import "framework/tables";
|
||||
@import "framework/notes";
|
||||
@import "framework/tabs";
|
||||
|
@ -49,16 +49,16 @@
|
|||
@import "framework/zen";
|
||||
@import "framework/blank";
|
||||
@import "framework/wells";
|
||||
@import "framework/page-header";
|
||||
@import "framework/page_header";
|
||||
@import "framework/awards";
|
||||
@import "framework/images";
|
||||
@import "framework/broadcast-messages";
|
||||
@import "framework/broadcast_messages";
|
||||
@import "framework/emojis";
|
||||
@import "framework/emoji-sprites";
|
||||
@import "framework/emoji_sprites";
|
||||
@import "framework/icons";
|
||||
@import "framework/snippets";
|
||||
@import "framework/memory_graph";
|
||||
@import "framework/responsive_tables";
|
||||
@import "framework/stacked-progress-bar";
|
||||
@import "framework/stacked_progress_bar";
|
||||
@import "framework/ci_variable_list";
|
||||
@import "framework/feature_highlight";
|
||||
|
|
|
@ -121,6 +121,10 @@
|
|||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.environment-child-row {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,26 +2,16 @@ class Import::BaseController < ApplicationController
|
|||
private
|
||||
|
||||
def find_or_create_namespace(names, owner)
|
||||
return current_user.namespace if names == owner
|
||||
return current_user.namespace unless current_user.can_create_group?
|
||||
|
||||
names = params[:target_namespace].presence || names
|
||||
full_path_namespace = Namespace.find_by_full_path(names)
|
||||
|
||||
return full_path_namespace if full_path_namespace
|
||||
return current_user.namespace if names == owner
|
||||
|
||||
names.split('/').inject(nil) do |parent, name|
|
||||
begin
|
||||
namespace = Group.create!(name: name,
|
||||
path: name,
|
||||
owner: current_user,
|
||||
parent: parent)
|
||||
namespace.add_owner(current_user)
|
||||
group = Groups::NestedCreateService.new(current_user, group_path: names).execute
|
||||
|
||||
namespace
|
||||
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
|
||||
Namespace.where(parent: parent).find_by_path_or_name(name)
|
||||
end
|
||||
end
|
||||
group.errors.any? ? current_user.namespace : group
|
||||
rescue => e
|
||||
Gitlab::AppLogger.error(e)
|
||||
|
||||
current_user.namespace
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,24 +37,30 @@ class Import::BitbucketController < Import::BaseController
|
|||
def create
|
||||
bitbucket_client = Bitbucket::Client.new(credentials)
|
||||
|
||||
@repo_id = params[:repo_id].to_s
|
||||
name = @repo_id.gsub('___', '/')
|
||||
repo_id = params[:repo_id].to_s
|
||||
name = repo_id.gsub('___', '/')
|
||||
repo = bitbucket_client.repo(name)
|
||||
@project_name = params[:new_name].presence || repo.name
|
||||
project_name = params[:new_name].presence || repo.name
|
||||
|
||||
repo_owner = repo.owner
|
||||
repo_owner = current_user.username if repo_owner == bitbucket_client.user.username
|
||||
namespace_path = params[:new_namespace].presence || repo_owner
|
||||
target_namespace = find_or_create_namespace(namespace_path, current_user)
|
||||
|
||||
@target_namespace = find_or_create_namespace(namespace_path, current_user)
|
||||
|
||||
if current_user.can?(:create_projects, @target_namespace)
|
||||
if current_user.can?(:create_projects, target_namespace)
|
||||
# The token in a session can be expired, we need to get most recent one because
|
||||
# Bitbucket::Connection class refreshes it.
|
||||
session[:bitbucket_token] = bitbucket_client.connection.token
|
||||
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, credentials).execute
|
||||
|
||||
project = Gitlab::BitbucketImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, credentials).execute
|
||||
|
||||
if project.persisted?
|
||||
render json: ProjectSerializer.new.represent(project)
|
||||
else
|
||||
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
render 'unauthorized'
|
||||
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -58,17 +58,17 @@ class Import::FogbugzController < Import::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
@repo_id = params[:repo_id]
|
||||
repo = client.repo(@repo_id)
|
||||
repo = client.repo(params[:repo_id])
|
||||
fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
|
||||
@target_namespace = current_user.namespace
|
||||
@project_name = repo.name
|
||||
|
||||
namespace = @target_namespace
|
||||
|
||||
umap = session[:fogbugz_user_map] || client.user_map
|
||||
|
||||
@project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, namespace, current_user, umap).execute
|
||||
project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, current_user.namespace, current_user, umap).execute
|
||||
|
||||
if project.persisted?
|
||||
render json: ProjectSerializer.new.represent(project)
|
||||
else
|
||||
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -36,16 +36,21 @@ class Import::GithubController < Import::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
@repo_id = params[:repo_id].to_i
|
||||
repo = client.repo(@repo_id)
|
||||
@project_name = params[:new_name].presence || repo.name
|
||||
repo = client.repo(params[:repo_id].to_i)
|
||||
project_name = params[:new_name].presence || repo.name
|
||||
namespace_path = params[:target_namespace].presence || current_user.namespace_path
|
||||
@target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
|
||||
target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
|
||||
|
||||
if can?(current_user, :create_projects, @target_namespace)
|
||||
@project = Gitlab::LegacyGithubImport::ProjectCreator.new(repo, @project_name, @target_namespace, current_user, access_params, type: provider).execute
|
||||
if can?(current_user, :create_projects, target_namespace)
|
||||
project = Gitlab::LegacyGithubImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, access_params, type: provider).execute
|
||||
|
||||
if project.persisted?
|
||||
render json: ProjectSerializer.new.represent(project)
|
||||
else
|
||||
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
render 'unauthorized'
|
||||
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -24,15 +24,19 @@ class Import::GitlabController < Import::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
@repo_id = params[:repo_id].to_i
|
||||
repo = client.project(@repo_id)
|
||||
@project_name = repo['name']
|
||||
@target_namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username'])
|
||||
repo = client.project(params[:repo_id].to_i)
|
||||
target_namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username'])
|
||||
|
||||
if current_user.can?(:create_projects, @target_namespace)
|
||||
@project = Gitlab::GitlabImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute
|
||||
if current_user.can?(:create_projects, target_namespace)
|
||||
project = Gitlab::GitlabImport::ProjectCreator.new(repo, target_namespace, current_user, access_params).execute
|
||||
|
||||
if project.persisted?
|
||||
render json: ProjectSerializer.new.represent(project)
|
||||
else
|
||||
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
render 'unauthorized'
|
||||
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -85,16 +85,16 @@ class Import::GoogleCodeController < Import::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
@repo_id = params[:repo_id]
|
||||
repo = client.repo(@repo_id)
|
||||
@target_namespace = current_user.namespace
|
||||
@project_name = repo.name
|
||||
|
||||
namespace = @target_namespace
|
||||
|
||||
repo = client.repo(params[:repo_id])
|
||||
user_map = session[:google_code_user_map]
|
||||
|
||||
@project = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, namespace, current_user, user_map).execute
|
||||
project = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, current_user.namespace, current_user, user_map).execute
|
||||
|
||||
if project.persisted?
|
||||
render json: ProjectSerializer.new.represent(project)
|
||||
else
|
||||
render json: { errors: project.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,14 +1,28 @@
|
|||
# Snippets Finder
|
||||
#
|
||||
# Used to filter Snippets collections by a set of params
|
||||
#
|
||||
# Arguments.
|
||||
#
|
||||
# current_user - The current user, nil also can be used.
|
||||
# params:
|
||||
# visibility (integer) - Individual snippet visibility: Public(20), internal(10) or private(0).
|
||||
# project (Project) - Project related.
|
||||
# author (User) - Author related.
|
||||
#
|
||||
# params are optional
|
||||
class SnippetsFinder < UnionFinder
|
||||
attr_accessor :current_user, :params
|
||||
include Gitlab::Allowable
|
||||
attr_accessor :current_user, :params, :project
|
||||
|
||||
def initialize(current_user, params = {})
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
@project = params[:project]
|
||||
end
|
||||
|
||||
def execute
|
||||
items = init_collection
|
||||
items = by_project(items)
|
||||
items = by_author(items)
|
||||
items = by_visibility(items)
|
||||
|
||||
|
@ -18,25 +32,42 @@ class SnippetsFinder < UnionFinder
|
|||
private
|
||||
|
||||
def init_collection
|
||||
items = Snippet.all
|
||||
|
||||
accessible(items)
|
||||
if project.present?
|
||||
authorized_snippets_from_project
|
||||
else
|
||||
authorized_snippets
|
||||
end
|
||||
end
|
||||
|
||||
def accessible(items)
|
||||
segments = []
|
||||
segments << items.public_to_user(current_user)
|
||||
segments << authorized_to_user(items) if current_user
|
||||
|
||||
find_union(segments, Snippet.includes(:author))
|
||||
def authorized_snippets_from_project
|
||||
if can?(current_user, :read_project_snippet, project)
|
||||
if project.team.member?(current_user)
|
||||
project.snippets
|
||||
else
|
||||
project.snippets.public_to_user(current_user)
|
||||
end
|
||||
else
|
||||
Snippet.none
|
||||
end
|
||||
end
|
||||
|
||||
def authorized_to_user(items)
|
||||
items.where(
|
||||
'author_id = :author_id
|
||||
OR project_id IN (:project_ids)',
|
||||
author_id: current_user.id,
|
||||
project_ids: current_user.authorized_projects.select(:id))
|
||||
def authorized_snippets
|
||||
Snippet.where(feature_available_projects.or(not_project_related)).public_or_visible_to_user(current_user)
|
||||
end
|
||||
|
||||
def feature_available_projects
|
||||
projects = Project.public_or_visible_to_user(current_user)
|
||||
.with_feature_available_for_user(:snippets, current_user).select(:id)
|
||||
arel_query = Arel::Nodes::SqlLiteral.new(projects.to_sql)
|
||||
table[:project_id].in(arel_query)
|
||||
end
|
||||
|
||||
def not_project_related
|
||||
table[:project_id].eq(nil)
|
||||
end
|
||||
|
||||
def table
|
||||
Snippet.arel_table
|
||||
end
|
||||
|
||||
def by_visibility(items)
|
||||
|
@ -53,12 +84,6 @@ class SnippetsFinder < UnionFinder
|
|||
items.where(author_id: params[:author].id)
|
||||
end
|
||||
|
||||
def by_project(items)
|
||||
return items unless params[:project]
|
||||
|
||||
items.where(project_id: params[:project].id)
|
||||
end
|
||||
|
||||
def visibility_from_scope
|
||||
case params[:scope].to_s
|
||||
when 'are_private'
|
||||
|
|
|
@ -1589,8 +1589,11 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def protected_for?(ref)
|
||||
ProtectedBranch.protected?(self, ref) ||
|
||||
if repository.branch_exists?(ref)
|
||||
ProtectedBranch.protected?(self, ref)
|
||||
elsif repository.tag_exists?(ref)
|
||||
ProtectedTag.protected?(self, ref)
|
||||
end
|
||||
end
|
||||
|
||||
def deployment_variables
|
||||
|
|
|
@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base
|
|||
@link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
|
||||
end
|
||||
|
||||
# Returns a collection of snippets that are either public or visible to the
|
||||
# logged in user.
|
||||
#
|
||||
# This method does not verify the user actually has the access to the project
|
||||
# the snippet is in, so it should be only used on a relation that's already scoped
|
||||
# for project access
|
||||
def self.public_or_visible_to_user(user = nil)
|
||||
if user
|
||||
authorized = user
|
||||
.project_authorizations
|
||||
.select(1)
|
||||
.where('project_authorizations.project_id = snippets.project_id')
|
||||
|
||||
levels = Gitlab::VisibilityLevel.levels_for_user(user)
|
||||
|
||||
where('EXISTS (?) OR snippets.visibility_level IN (?) or snippets.author_id = (?)', authorized, levels, user.id)
|
||||
else
|
||||
public_to_user
|
||||
end
|
||||
end
|
||||
|
||||
def to_reference(from = nil, full: false)
|
||||
reference = "#{self.class.reference_prefix}#{id}"
|
||||
|
||||
|
|
|
@ -119,7 +119,6 @@ class ProjectPolicy < BasePolicy
|
|||
enable :create_note
|
||||
enable :upload_file
|
||||
enable :read_cycle_analytics
|
||||
enable :read_project_snippet
|
||||
end
|
||||
|
||||
rule { can?(:reporter_access) }.policy do
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
class ProjectSerializer < BaseSerializer
|
||||
entity ProjectEntity
|
||||
end
|
|
@ -11,8 +11,8 @@ module Groups
|
|||
def execute
|
||||
return nil unless group_path
|
||||
|
||||
if group = Group.find_by_full_path(group_path)
|
||||
return group
|
||||
if namespace = namespace_or_group(group_path)
|
||||
return namespace
|
||||
end
|
||||
|
||||
if group_path.include?('/') && !Group.supports_nested_groups?
|
||||
|
@ -40,10 +40,14 @@ module Groups
|
|||
)
|
||||
new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility
|
||||
|
||||
last_group = Group.find_by_full_path(partial_path) || Groups::CreateService.new(current_user, new_params).execute
|
||||
last_group = namespace_or_group(partial_path) || Groups::CreateService.new(current_user, new_params).execute
|
||||
end
|
||||
|
||||
last_group
|
||||
end
|
||||
|
||||
def namespace_or_group(group_path)
|
||||
Namespace.find_by_full_path(group_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
- id = variable&.id
|
||||
- key = variable&.key
|
||||
- value = variable&.value
|
||||
- is_protected = variable && !only_key_value ? variable.protected : true
|
||||
- is_protected = variable && !only_key_value ? variable.protected : false
|
||||
|
||||
- id_input_name = "#{form_field}[variables_attributes][][id]"
|
||||
- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
- if @project.persisted?
|
||||
:plain
|
||||
job = $("tr#repo_#{@repo_id}")
|
||||
job.attr("id", "project_#{@project.id}")
|
||||
target_field = job.find(".import-target")
|
||||
target_field.empty()
|
||||
target_field.append('#{link_to @project.full_path, project_path(@project)}')
|
||||
$("table.import-jobs tbody").prepend(job)
|
||||
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
|
||||
- else
|
||||
:plain
|
||||
job = $("tr#repo_#{@repo_id}")
|
||||
job.find(".import-actions").html("<i class='fa fa-exclamation-circle'></i> Error saving project: #{escape_javascript(h(@project.errors.full_messages.join(',')))}")
|
|
@ -1,14 +0,0 @@
|
|||
:plain
|
||||
tr = $("tr#repo_#{@repo_id}")
|
||||
target_field = tr.find(".import-target")
|
||||
import_button = tr.find(".btn-import")
|
||||
origin_target = target_field.text()
|
||||
project_name = "#{@project_name}"
|
||||
origin_namespace = "#{@target_namespace.full_path}"
|
||||
target_field.empty()
|
||||
target_field.append("<p class='alert alert-danger'>This namespace has already been taken! Please choose another one.</p>")
|
||||
target_field.append("<input type='text' name='target_namespace' />")
|
||||
target_field.append("/" + project_name)
|
||||
target_field.data("project_name", project_name)
|
||||
target_field.find('input').prop("value", origin_namespace)
|
||||
import_button.enable().removeClass('is-loading')
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds tooltip in environment names to increase readability
|
||||
merge_request:
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update vue component naming guidelines
|
||||
merge_request: 17018
|
||||
author: George Tsiolis
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move IssuableTimeTracker vue component
|
||||
merge_request: 16948
|
||||
author: George Tsiolis
|
||||
type: performance
|
|
@ -207,10 +207,39 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
|
|||
var c = pureFunction(values.foo);
|
||||
```
|
||||
|
||||
1. Avoid constructors with side-effects
|
||||
1. Avoid constructors with side-effects.
|
||||
Although we aim for code without side-effects we need some side-effects for our code to run.
|
||||
|
||||
If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
|
||||
|
||||
```javascript
|
||||
// Bad
|
||||
export class Foo {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
init() {
|
||||
document.addEventListener('click', this.handleCallback)
|
||||
},
|
||||
handleCallback() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Good
|
||||
export class Foo {
|
||||
constructor() {
|
||||
document.addEventListener()
|
||||
}
|
||||
handleCallback() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized oustside of the constructor.
|
||||
|
||||
1. Prefer `.map`, `.reduce` or `.filter` over `.forEach`
|
||||
A forEach will cause side effects, it will be mutating the array being iterated. Prefer using `.map`,
|
||||
A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`,
|
||||
`.reduce` or `.filter`
|
||||
```javascript
|
||||
const users = [ { name: 'Foo' }, { name: 'Bar' } ];
|
||||
|
@ -302,20 +331,20 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
|
|||
|
||||
#### Naming
|
||||
1. **Extensions**: Use `.vue` extension for Vue components.
|
||||
1. **Reference Naming**: Use camelCase for their instances:
|
||||
1. **Reference Naming**: Use PascalCase for their instances:
|
||||
```javascript
|
||||
// bad
|
||||
import CardBoard from 'cardBoard'
|
||||
import cardBoard from 'cardBoard.vue'
|
||||
|
||||
components: {
|
||||
CardBoard:
|
||||
cardBoard,
|
||||
};
|
||||
|
||||
// good
|
||||
import cardBoard from 'cardBoard'
|
||||
import CardBoard from 'cardBoard.vue'
|
||||
|
||||
components: {
|
||||
cardBoard:
|
||||
CardBoard,
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
@ -85,9 +85,9 @@ module API
|
|||
use :pagination
|
||||
end
|
||||
get ':id/-/search' do
|
||||
find_group!(params[:id])
|
||||
group = find_group!(params[:id])
|
||||
|
||||
present search(group_id: params[:id]), with: entity
|
||||
present search(group_id: group.id), with: entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -106,9 +106,9 @@ module API
|
|||
use :pagination
|
||||
end
|
||||
get ':id/-/search' do
|
||||
find_project!(params[:id])
|
||||
project = find_project!(params[:id])
|
||||
|
||||
present search(project_id: params[:id]), with: entity
|
||||
present search(project_id: project.id), with: entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -60,7 +60,7 @@ module API
|
|||
end
|
||||
post ':id/mark_as_done' do
|
||||
TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
|
||||
todo = Todo.find(params[:id])
|
||||
todo = current_user.todos.find(params[:id])
|
||||
|
||||
present todo, with: Entities::Todo, current_user: current_user
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ module API
|
|||
end
|
||||
delete ':id' do
|
||||
TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
|
||||
todo = Todo.find(params[:id])
|
||||
todo = current_user.todos.find(params[:id])
|
||||
|
||||
present todo, with: ::API::Entities::Todo, current_user: current_user
|
||||
end
|
||||
|
|
|
@ -14,23 +14,33 @@ module Banzai
|
|||
end
|
||||
|
||||
def highlight_node(node)
|
||||
code = node.text
|
||||
css_classes = 'code highlight js-syntax-highlight'
|
||||
language = node.attr('lang')
|
||||
lang = node.attr('lang')
|
||||
retried = false
|
||||
|
||||
if use_rouge?(language)
|
||||
lexer = lexer_for(language)
|
||||
if use_rouge?(lang)
|
||||
lexer = lexer_for(lang)
|
||||
language = lexer.tag
|
||||
else
|
||||
lexer = Rouge::Lexers::PlainText.new
|
||||
language = lang
|
||||
end
|
||||
|
||||
begin
|
||||
code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: language)
|
||||
css_classes << " #{language}"
|
||||
rescue
|
||||
# Gracefully handle syntax highlighter bugs/errors to ensure
|
||||
# users can still access an issue/comment/etc.
|
||||
begin
|
||||
code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, node.text), tag: language)
|
||||
css_classes << " #{language}" if language
|
||||
rescue
|
||||
# Gracefully handle syntax highlighter bugs/errors to ensure users can
|
||||
# still access an issue/comment/etc. First, retry with the plain text
|
||||
# filter. If that fails, then just skip this entirely, but that would
|
||||
# be a pretty bad upstream bug.
|
||||
return if retried
|
||||
|
||||
language = nil
|
||||
end
|
||||
language = nil
|
||||
lexer = Rouge::Lexers::PlainText.new
|
||||
retried = true
|
||||
|
||||
retry
|
||||
end
|
||||
|
||||
highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
|
||||
|
|
|
@ -28,9 +28,9 @@ module Gitlab
|
|||
|
||||
# encode and clean the bad chars
|
||||
message.replace clean(message)
|
||||
rescue ArgumentError
|
||||
return nil
|
||||
rescue
|
||||
rescue ArgumentError => e
|
||||
return unless e.message.include?('unknown encoding name')
|
||||
|
||||
encoding = detect ? detect[:encoding] : "unknown"
|
||||
"--broken encoding: #{encoding}"
|
||||
end
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace :gemojione do
|
|||
end
|
||||
end
|
||||
|
||||
style_path = Rails.root.join(*%w(app assets stylesheets framework emoji-sprites.scss))
|
||||
style_path = Rails.root.join(*%w(app assets stylesheets framework emoji_sprites.scss))
|
||||
|
||||
# Combine the resized assets into a packed sprite and re-generate the SCSS
|
||||
SpriteFactory.cssurl = "image-url('$IMAGE')"
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
"jed": "^1.1.1",
|
||||
"jquery": "^2.2.4",
|
||||
"jquery-ujs": "1.2.2",
|
||||
"jquery.waitforimages": "^2.2.0",
|
||||
"js-cookie": "^2.1.3",
|
||||
"jszip": "^3.1.3",
|
||||
"jszip-utils": "^0.0.2",
|
||||
|
|
24
qa/README.md
24
qa/README.md
|
@ -70,6 +70,30 @@ If you need to authenticate as a different user, you can provide the
|
|||
GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance https://gitlab.example.com
|
||||
```
|
||||
|
||||
If your user doesn't have permission to default sandbox group
|
||||
`gitlab-qa-sandbox`, you could also use another sandbox group by giving
|
||||
`GITLAB_SANDBOX_NAME`:
|
||||
|
||||
```
|
||||
GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com
|
||||
```
|
||||
|
||||
In addition, the `GITLAB_USER_TYPE` can be set to "ldap" to sign in as an LDAP user:
|
||||
|
||||
```
|
||||
GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com
|
||||
```
|
||||
|
||||
All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables).
|
||||
|
||||
### Building a Docker image to test
|
||||
|
||||
Once you have made changes to the CE/EE repositories, you may want to build a
|
||||
Docker image to test locally instead of waiting for the `gitlab-ce-qa` or
|
||||
`gitlab-ee-qa` nightly builds. To do that, you can run from this directory:
|
||||
|
||||
```sh
|
||||
docker build -t gitlab/gitlab-ce-qa:nightly .
|
||||
```
|
||||
|
||||
[GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/
|
||||
|
|
|
@ -39,6 +39,14 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def sign_in_using_credentials
|
||||
if Runtime::User.ldap_user?
|
||||
sign_in_using_ldap_credentials
|
||||
else
|
||||
sign_in_using_gitlab_credentials
|
||||
end
|
||||
end
|
||||
|
||||
def sign_in_using_ldap_credentials
|
||||
using_wait_time 0 do
|
||||
set_initial_password_if_present
|
||||
|
@ -51,7 +59,7 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def sign_in_using_credentials
|
||||
def sign_in_using_gitlab_credentials
|
||||
using_wait_time 0 do
|
||||
set_initial_password_if_present
|
||||
|
||||
|
|
|
@ -16,6 +16,28 @@ module QA
|
|||
def personal_access_token
|
||||
ENV['PERSONAL_ACCESS_TOKEN']
|
||||
end
|
||||
|
||||
# By default, "standard" denotes a standard GitLab user login.
|
||||
# Set this to "ldap" if the user should be logged in via LDAP.
|
||||
def user_type
|
||||
(ENV['GITLAB_USER_TYPE'] || 'standard').tap do |type|
|
||||
unless %w(ldap standard).include?(type)
|
||||
raise ArgumentError.new("Invalid user type '#{type}': must be 'ldap' or 'standard'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_username
|
||||
ENV['GITLAB_USERNAME']
|
||||
end
|
||||
|
||||
def user_password
|
||||
ENV['GITLAB_PASSWORD']
|
||||
end
|
||||
|
||||
def sandbox_name
|
||||
ENV['GITLAB_SANDBOX_NAME']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ module QA
|
|||
end
|
||||
|
||||
def sandbox_name
|
||||
'gitlab-qa-sandbox'
|
||||
Runtime::Env.sandbox_name || 'gitlab-qa-sandbox'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,11 +4,15 @@ module QA
|
|||
extend self
|
||||
|
||||
def name
|
||||
ENV['GITLAB_USERNAME'] || 'root'
|
||||
Runtime::Env.user_username || 'root'
|
||||
end
|
||||
|
||||
def password
|
||||
ENV['GITLAB_PASSWORD'] || '5iveL!fe'
|
||||
Runtime::Env.user_password || '5iveL!fe'
|
||||
end
|
||||
|
||||
def ldap_user?
|
||||
Runtime::Env.user_type == 'ldap'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,14 @@ module QA
|
|||
@tags = %w[qa test]
|
||||
end
|
||||
|
||||
def network
|
||||
shell "docker network inspect #{@network}"
|
||||
rescue CommandError
|
||||
'bridge'
|
||||
else
|
||||
@network
|
||||
end
|
||||
|
||||
def pull
|
||||
shell "docker pull #{@image}"
|
||||
end
|
||||
|
@ -22,7 +30,7 @@ module QA
|
|||
def register!
|
||||
shell <<~CMD.tr("\n", ' ')
|
||||
docker run -d --rm --entrypoint=/bin/sh
|
||||
--network #{@network} --name #{@name}
|
||||
--network #{network} --name #{@name}
|
||||
-e CI_SERVER_URL=#{@address}
|
||||
-e REGISTER_NON_INTERACTIVE=true
|
||||
-e REGISTRATION_TOKEN=#{@token}
|
||||
|
|
|
@ -3,6 +3,8 @@ require 'open3'
|
|||
module QA
|
||||
module Service
|
||||
module Shellout
|
||||
CommandError = Class.new(StandardError)
|
||||
|
||||
##
|
||||
# TODO, make it possible to use generic QA framework classes
|
||||
# as a library - gitlab-org/gitlab-qa#94
|
||||
|
@ -14,7 +16,7 @@ module QA
|
|||
out.each { |line| puts line }
|
||||
|
||||
if wait.value.exited? && wait.value.exitstatus.nonzero?
|
||||
raise "Command `#{command}` failed!"
|
||||
raise CommandError, "Command `#{command}` failed!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,4 +55,25 @@ describe QA::Runtime::Env do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.user_type' do
|
||||
it 'returns standard if not defined' do
|
||||
expect(described_class.user_type).to eq('standard')
|
||||
end
|
||||
|
||||
it 'returns standard as defined' do
|
||||
stub_env('GITLAB_USER_TYPE', 'standard')
|
||||
expect(described_class.user_type).to eq('standard')
|
||||
end
|
||||
|
||||
it 'returns ldap as defined' do
|
||||
stub_env('GITLAB_USER_TYPE', 'ldap')
|
||||
expect(described_class.user_type).to eq('ldap')
|
||||
end
|
||||
|
||||
it 'returns an error if invalid user type' do
|
||||
stub_env('GITLAB_USER_TYPE', 'foobar')
|
||||
expect { described_class.user_type }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -84,20 +84,42 @@ describe Import::BitbucketController do
|
|||
double(slug: "vim", owner: bitbucket_username, name: 'vim')
|
||||
end
|
||||
|
||||
let(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(Bitbucket::Client).to receive(:repo).and_return(bitbucket_repo)
|
||||
allow_any_instance_of(Bitbucket::Client).to receive(:user).and_return(bitbucket_user)
|
||||
assign_session_tokens
|
||||
end
|
||||
|
||||
it 'returns 200 response when the project is imported successfully' do
|
||||
allow(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :json
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns 422 response when the project could not be imported' do
|
||||
allow(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
|
||||
.and_return(double(execute: build(:project)))
|
||||
|
||||
post :create, format: :json
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
|
||||
context "when the repository owner is the Bitbucket user" do
|
||||
context "when the Bitbucket user and GitLab user's usernames match" do
|
||||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -107,9 +129,9 @@ describe Import::BitbucketController do
|
|||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -120,7 +142,7 @@ describe Import::BitbucketController do
|
|||
allow(controller).to receive(:current_user).and_return(user)
|
||||
allow(user).to receive(:can?).and_return(false)
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -143,9 +165,9 @@ describe Import::BitbucketController do
|
|||
it "takes the existing namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -154,7 +176,7 @@ describe Import::BitbucketController do
|
|||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.not_to receive(:new)
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -163,17 +185,17 @@ describe Import::BitbucketController do
|
|||
context "when current user can create namespaces" do
|
||||
it "creates the namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).and_return(double(execute: true))
|
||||
.to receive(:new).and_return(double(execute: project))
|
||||
|
||||
expect { post :create, format: :js }.to change(Namespace, :count).by(1)
|
||||
expect { post :create, format: :json }.to change(Namespace, :count).by(1)
|
||||
end
|
||||
|
||||
it "takes the new namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, bitbucket_repo.name, an_instance_of(Group), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -184,23 +206,23 @@ describe Import::BitbucketController do
|
|||
|
||||
it "doesn't create the namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).and_return(double(execute: true))
|
||||
.to receive(:new).and_return(double(execute: project))
|
||||
|
||||
expect { post :create, format: :js }.not_to change(Namespace, :count)
|
||||
expect { post :create, format: :json }.not_to change(Namespace, :count)
|
||||
end
|
||||
|
||||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, bitbucket_repo.name, user.namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen an existing nested namespace and name for the project' do
|
||||
context 'user has chosen an existing nested namespace and name for the project', :postgresql do
|
||||
let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
|
||||
let(:test_name) { 'test_name' }
|
||||
|
@ -212,63 +234,77 @@ describe Import::BitbucketController do
|
|||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json }
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen a non-existent nested namespaces and name for the project' do
|
||||
context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
|
||||
let(:test_name) { 'test_name' }
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
|
||||
end
|
||||
|
||||
it 'creates the namespaces' do
|
||||
allow(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } }
|
||||
expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } }
|
||||
.to change { Namespace.count }.by(2)
|
||||
end
|
||||
|
||||
it 'new namespace has the right parent' do
|
||||
allow(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
|
||||
|
||||
expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
|
||||
before do
|
||||
parent_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json }
|
||||
end
|
||||
|
||||
it 'creates the namespaces' do
|
||||
allow(Gitlab::BitbucketImport::ProjectCreator)
|
||||
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } }
|
||||
expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } }
|
||||
.to change { Namespace.count }.by(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can not create projects in the chosen namespace' do
|
||||
it 'returns 422 response' do
|
||||
other_namespace = create(:group, name: 'other_namespace')
|
||||
|
||||
post :create, { target_namespace: other_namespace.name, format: :json }
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,6 +57,7 @@ describe Import::GitlabController do
|
|||
end
|
||||
|
||||
describe "POST create" do
|
||||
let(:project) { create(:project) }
|
||||
let(:gitlab_username) { user.username }
|
||||
let(:gitlab_user) do
|
||||
{ username: gitlab_username }.with_indifferent_access
|
||||
|
@ -75,14 +76,34 @@ describe Import::GitlabController do
|
|||
assign_session_token
|
||||
end
|
||||
|
||||
it 'returns 200 response when the project is imported successfully' do
|
||||
allow(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :json
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns 422 response when the project could not be imported' do
|
||||
allow(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
|
||||
.and_return(double(execute: build(:project)))
|
||||
|
||||
post :create, format: :json
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
|
||||
context "when the repository owner is the GitLab.com user" do
|
||||
context "when the GitLab.com user and GitLab server user's usernames match" do
|
||||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -92,9 +113,9 @@ describe Import::GitlabController do
|
|||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -118,9 +139,9 @@ describe Import::GitlabController do
|
|||
it "takes the existing namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, existing_namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -129,7 +150,7 @@ describe Import::GitlabController do
|
|||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.not_to receive(:new)
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -138,17 +159,17 @@ describe Import::GitlabController do
|
|||
context "when current user can create namespaces" do
|
||||
it "creates the namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).and_return(double(execute: true))
|
||||
.to receive(:new).and_return(double(execute: project))
|
||||
|
||||
expect { post :create, format: :js }.to change(Namespace, :count).by(1)
|
||||
expect { post :create, format: :json }.to change(Namespace, :count).by(1)
|
||||
end
|
||||
|
||||
it "takes the new namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, an_instance_of(Group), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,22 +180,22 @@ describe Import::GitlabController do
|
|||
|
||||
it "doesn't create the namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).and_return(double(execute: true))
|
||||
.to receive(:new).and_return(double(execute: project))
|
||||
|
||||
expect { post :create, format: :js }.not_to change(Namespace, :count)
|
||||
expect { post :create, format: :json }.not_to change(Namespace, :count)
|
||||
end
|
||||
|
||||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, user.namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen an existing nested namespace for the project' do
|
||||
context 'user has chosen an existing nested namespace for the project', :postgresql do
|
||||
let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
|
||||
|
||||
|
@ -185,64 +206,78 @@ describe Import::GitlabController do
|
|||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, nested_namespace, user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: nested_namespace.full_path, format: :js }
|
||||
post :create, { target_namespace: nested_namespace.full_path, format: :json }
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen a non-existent nested namespaces for the project' do
|
||||
context 'user has chosen a non-existent nested namespaces for the project', :postgresql do
|
||||
let(:test_name) { 'test_name' }
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/bar', format: :js }
|
||||
post :create, { target_namespace: 'foo/bar', format: :json }
|
||||
end
|
||||
|
||||
it 'creates the namespaces' do
|
||||
allow(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
expect { post :create, { target_namespace: 'foo/bar', format: :js } }
|
||||
expect { post :create, { target_namespace: 'foo/bar', format: :json } }
|
||||
.to change { Namespace.count }.by(2)
|
||||
end
|
||||
|
||||
it 'new namespace has the right parent' do
|
||||
allow(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/bar', format: :js }
|
||||
post :create, { target_namespace: 'foo/bar', format: :json }
|
||||
|
||||
expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
|
||||
before do
|
||||
parent_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/foobar/bar', format: :js }
|
||||
post :create, { target_namespace: 'foo/foobar/bar', format: :json }
|
||||
end
|
||||
|
||||
it 'creates the namespaces' do
|
||||
allow(Gitlab::GitlabImport::ProjectCreator)
|
||||
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
expect { post :create, { target_namespace: 'foo/foobar/bar', format: :js } }
|
||||
expect { post :create, { target_namespace: 'foo/foobar/bar', format: :json } }
|
||||
.to change { Namespace.count }.by(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can not create projects in the chosen namespace' do
|
||||
it 'returns 422 response' do
|
||||
other_namespace = create(:group, name: 'other_namespace')
|
||||
|
||||
post :create, { target_namespace: other_namespace.name, format: :json }
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Math rendering', :js do
|
||||
it 'renders inline and display math correctly' do
|
||||
description = <<~MATH
|
||||
This math is inline $`a^2+b^2=c^2`$.
|
||||
|
||||
This is on a separate line
|
||||
```math
|
||||
a^2+b^2=c^2
|
||||
```
|
||||
MATH
|
||||
|
||||
project = create(:project, :public)
|
||||
issue = create(:issue, project: project, description: description)
|
||||
|
||||
visit project_issue_path(project, issue)
|
||||
|
||||
expect(page).to have_selector('.katex .mord.mathit', text: 'b')
|
||||
expect(page).to have_selector('.katex-display .mord.mathit', text: 'b')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'Mermaid rendering', :js do
|
||||
it 'renders Mermaid diagrams correctly' do
|
||||
description = <<~MERMAID
|
||||
```mermaid
|
||||
graph TD;
|
||||
A-->B;
|
||||
A-->C;
|
||||
B-->D;
|
||||
C-->D;
|
||||
```
|
||||
MERMAID
|
||||
|
||||
project = create(:project, :public)
|
||||
issue = create(:issue, project: project, description: description)
|
||||
|
||||
visit project_issue_path(project, issue)
|
||||
|
||||
%w[A B C D].each do |label|
|
||||
expect(page).to have_selector('svg foreignObject', text: label)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,57 +1,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe SnippetsFinder do
|
||||
let(:user) { create :user }
|
||||
let(:user1) { create :user }
|
||||
let(:group) { create :group, :public }
|
||||
|
||||
let(:project1) { create(:project, :public, group: group) }
|
||||
let(:project2) { create(:project, :private, group: group) }
|
||||
|
||||
context 'all snippets visible to a user' do
|
||||
let!(:snippet1) { create(:personal_snippet, :private) }
|
||||
let!(:snippet2) { create(:personal_snippet, :internal) }
|
||||
let!(:snippet3) { create(:personal_snippet, :public) }
|
||||
let!(:project_snippet1) { create(:project_snippet, :private) }
|
||||
let!(:project_snippet2) { create(:project_snippet, :internal) }
|
||||
let!(:project_snippet3) { create(:project_snippet, :public) }
|
||||
|
||||
it "returns all private and internal snippets" do
|
||||
snippets = described_class.new(user, scope: :all).execute
|
||||
expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3)
|
||||
expect(snippets).not_to include(snippet1, project_snippet1)
|
||||
end
|
||||
|
||||
it "returns all public snippets" do
|
||||
snippets = described_class.new(nil, scope: :all).execute
|
||||
expect(snippets).to include(snippet3, project_snippet3)
|
||||
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
|
||||
end
|
||||
|
||||
it "returns all public and internal snippets for normal user" do
|
||||
snippets = described_class.new(user).execute
|
||||
|
||||
expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3)
|
||||
expect(snippets).not_to include(snippet1, project_snippet1)
|
||||
end
|
||||
|
||||
it "returns all public snippets for non authorized user" do
|
||||
snippets = described_class.new(nil).execute
|
||||
|
||||
expect(snippets).to include(snippet3, project_snippet3)
|
||||
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
|
||||
end
|
||||
|
||||
it "returns all public and authored snippets for external user" do
|
||||
external_user = create(:user, :external)
|
||||
authored_snippet = create(:personal_snippet, :internal, author: external_user)
|
||||
|
||||
snippets = described_class.new(external_user).execute
|
||||
|
||||
expect(snippets).to include(snippet3, project_snippet3, authored_snippet)
|
||||
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
|
||||
end
|
||||
end
|
||||
include Gitlab::Allowable
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'filter by visibility' do
|
||||
let!(:snippet1) { create(:personal_snippet, :private) }
|
||||
|
@ -67,6 +18,7 @@ describe SnippetsFinder do
|
|||
end
|
||||
|
||||
context 'filter by scope' do
|
||||
let(:user) { create :user }
|
||||
let!(:snippet1) { create(:personal_snippet, :private, author: user) }
|
||||
let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
|
||||
let!(:snippet3) { create(:personal_snippet, :public, author: user) }
|
||||
|
@ -84,7 +36,7 @@ describe SnippetsFinder do
|
|||
expect(snippets).not_to include(snippet2, snippet3)
|
||||
end
|
||||
|
||||
it "returns all snippets for 'are_interna;' scope" do
|
||||
it "returns all snippets for 'are_internal' scope" do
|
||||
snippets = described_class.new(user, scope: :are_internal).execute
|
||||
|
||||
expect(snippets).to include(snippet2)
|
||||
|
@ -100,6 +52,8 @@ describe SnippetsFinder do
|
|||
end
|
||||
|
||||
context 'filter by author' do
|
||||
let(:user) { create :user }
|
||||
let(:user1) { create :user }
|
||||
let!(:snippet1) { create(:personal_snippet, :private, author: user) }
|
||||
let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
|
||||
let!(:snippet3) { create(:personal_snippet, :public, author: user) }
|
||||
|
@ -147,6 +101,10 @@ describe SnippetsFinder do
|
|||
end
|
||||
|
||||
context 'filter by project' do
|
||||
let(:user) { create :user }
|
||||
let(:group) { create :group, :public }
|
||||
let(:project1) { create(:project, :public, group: group) }
|
||||
|
||||
before do
|
||||
@snippet1 = create(:project_snippet, :private, project: project1)
|
||||
@snippet2 = create(:project_snippet, :internal, project: project1)
|
||||
|
@ -203,4 +161,9 @@ describe SnippetsFinder do
|
|||
expect(snippets).to include(@snippet1)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#execute" do
|
||||
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
|
||||
include_examples 'snippet visibility', described_class
|
||||
end
|
||||
end
|
||||
|
|
|
@ -126,7 +126,7 @@ describe('VariableList', () => {
|
|||
|
||||
// Check for the correct default in the new row
|
||||
const $protectedInput = $wrapper.find('.js-row:last-child').find('.js-ci-variable-input-protected');
|
||||
expect($protectedInput.val()).toBe('true');
|
||||
expect($protectedInput.val()).toBe('false');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import { ImporterStatus } from '~/importer_status';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
describe('Importer Status', () => {
|
||||
describe('addToImport', () => {
|
||||
let instance;
|
||||
let mock;
|
||||
const importUrl = '/import_url';
|
||||
|
||||
beforeEach(() => {
|
||||
setFixtures(`
|
||||
<tr id="repo_123">
|
||||
<td class="import-target"></td>
|
||||
<td class="import-actions job-status">
|
||||
<button name="button" type="submit" class="btn btn-import js-add-to-import">
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
spyOn(ImporterStatus.prototype, 'initStatusPage').and.callFake(() => {});
|
||||
spyOn(ImporterStatus.prototype, 'setAutoUpdate').and.callFake(() => {});
|
||||
instance = new ImporterStatus('', importUrl);
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('sets table row to active after post request', (done) => {
|
||||
mock.onPost(importUrl).reply(200, {
|
||||
id: 1,
|
||||
full_path: '/full_path',
|
||||
});
|
||||
|
||||
instance.addToImport({
|
||||
currentTarget: document.querySelector('.js-add-to-import'),
|
||||
})
|
||||
.then(() => {
|
||||
expect(document.querySelector('tr').classList.contains('active')).toEqual(true);
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
|
||||
import timeTracker from '~/sidebar/components/time_tracking/time_tracker';
|
||||
import timeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
|
||||
|
||||
function initTimeTrackingComponent(opts) {
|
||||
setFixtures(`
|
||||
|
|
|
@ -1,38 +1,22 @@
|
|||
import Vue from 'vue';
|
||||
import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch';
|
||||
|
||||
const createComponent = () => {
|
||||
const Component = Vue.extend(missingBranchComponent);
|
||||
const mr = {
|
||||
sourceBranchRemoved: true,
|
||||
};
|
||||
|
||||
return new Component({
|
||||
el: document.createElement('div'),
|
||||
propsData: { mr },
|
||||
});
|
||||
};
|
||||
import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
|
||||
import mountComponent from '../../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('MRWidgetMissingBranch', () => {
|
||||
describe('props', () => {
|
||||
it('should have props', () => {
|
||||
const mrProp = missingBranchComponent.props.mr;
|
||||
let vm;
|
||||
|
||||
expect(mrProp.type instanceof Object).toBeTruthy();
|
||||
expect(mrProp.required).toBeTruthy();
|
||||
});
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(missingBranchComponent);
|
||||
vm = mountComponent(Component, { mr: { sourceBranchRemoved: true } });
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
it('should have components added', () => {
|
||||
expect(missingBranchComponent.components['mr-widget-merge-help']).toBeDefined();
|
||||
});
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('missingBranchName', () => {
|
||||
it('should return proper branch name', () => {
|
||||
const vm = createComponent();
|
||||
expect(vm.missingBranchName).toEqual('source');
|
||||
|
||||
vm.mr.sourceBranchRemoved = false;
|
||||
|
@ -43,7 +27,7 @@ describe('MRWidgetMissingBranch', () => {
|
|||
|
||||
describe('template', () => {
|
||||
it('should have correct elements', () => {
|
||||
const el = createComponent().$el;
|
||||
const el = vm.$el;
|
||||
const content = el.textContent.replace(/\n(\s)+/g, ' ').trim();
|
||||
|
||||
expect(el.classList.contains('mr-widget-body')).toBeTruthy();
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import confirmationInput from '~/vue_shared/components/confirmation_input.vue';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Confirmation input component', () => {
|
||||
const Component = Vue.extend(confirmationInput);
|
||||
const props = {
|
||||
inputId: 'dummy-id',
|
||||
confirmationKey: 'confirmation-key',
|
||||
confirmationValue: 'confirmation-value',
|
||||
};
|
||||
let vm;
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('props', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, props);
|
||||
});
|
||||
|
||||
it('sets id of the input field to inputId', () => {
|
||||
expect(vm.$refs.enteredValue.id).toBe(props.inputId);
|
||||
});
|
||||
|
||||
it('sets name of the input field to confirmationKey', () => {
|
||||
expect(vm.$refs.enteredValue.name).toBe(props.confirmationKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('inputLabel', () => {
|
||||
it('escapes confirmationValue by default', () => {
|
||||
vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng' });
|
||||
expect(vm.inputLabel).toBe('Type <code>n<e></e>ds escap"ng</code> to confirm:');
|
||||
});
|
||||
|
||||
it('does not escape confirmationValue if escapeValue is false', () => {
|
||||
vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng', shouldEscapeConfirmationValue: false });
|
||||
expect(vm.inputLabel).toBe('Type <code>n<e></e>ds escap"ng</code> to confirm:');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('hasCorrectValue', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, props);
|
||||
});
|
||||
|
||||
it('returns false if entered value is incorrect', () => {
|
||||
vm.$refs.enteredValue.value = 'incorrect';
|
||||
expect(vm.hasCorrectValue()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if entered value is correct', () => {
|
||||
vm.$refs.enteredValue.value = props.confirmationValue;
|
||||
expect(vm.hasCorrectValue()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3,35 +3,86 @@ require 'spec_helper'
|
|||
describe Banzai::Filter::SyntaxHighlightFilter do
|
||||
include FilterSpecHelper
|
||||
|
||||
shared_examples "XSS prevention" do |lang|
|
||||
it "escapes HTML tags" do
|
||||
# This is how a script tag inside a code block is presented to this filter
|
||||
# after Markdown rendering.
|
||||
result = filter(%{<pre lang="#{lang}"><code><script>alert(1)</script></code></pre>})
|
||||
|
||||
expect(result.to_html).not_to include("<script>alert(1)</script>")
|
||||
expect(result.to_html).to include("alert(1)")
|
||||
end
|
||||
end
|
||||
|
||||
context "when no language is specified" do
|
||||
it "highlights as plaintext" do
|
||||
result = filter('<pre><code>def fun end</code></pre>')
|
||||
|
||||
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>')
|
||||
end
|
||||
|
||||
include_examples "XSS prevention", ""
|
||||
end
|
||||
|
||||
context "when a valid language is specified" do
|
||||
it "highlights as that language" do
|
||||
result = filter('<pre><code lang="ruby">def fun end</code></pre>')
|
||||
|
||||
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>')
|
||||
end
|
||||
|
||||
include_examples "XSS prevention", "ruby"
|
||||
end
|
||||
|
||||
context "when an invalid language is specified" do
|
||||
it "highlights as plaintext" do
|
||||
result = filter('<pre><code lang="gnuplot">This is a test</code></pre>')
|
||||
|
||||
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>')
|
||||
end
|
||||
|
||||
include_examples "XSS prevention", "gnuplot"
|
||||
end
|
||||
|
||||
context "languages that should be passed through" do
|
||||
%w(math mermaid plantuml).each do |lang|
|
||||
context "when #{lang} is specified" do
|
||||
it "highlights as plaintext but with the correct language attribute and class" do
|
||||
result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>})
|
||||
|
||||
expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
|
||||
end
|
||||
|
||||
include_examples "XSS prevention", lang
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when Rouge formatting fails" do
|
||||
context "when Rouge lexing fails" do
|
||||
before do
|
||||
allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError)
|
||||
allow_any_instance_of(Rouge::Lexers::Ruby).to receive(:stream_tokens).and_raise(StandardError)
|
||||
end
|
||||
|
||||
it "highlights as plaintext" do
|
||||
result = filter('<pre><code lang="ruby">This is a test</code></pre>')
|
||||
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code>This is a test</code></pre>')
|
||||
|
||||
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre>')
|
||||
end
|
||||
|
||||
include_examples "XSS prevention", "ruby"
|
||||
end
|
||||
|
||||
context "when Rouge lexing fails after a retry" do
|
||||
before do
|
||||
allow_any_instance_of(Rouge::Lexers::PlainText).to receive(:stream_tokens).and_raise(StandardError)
|
||||
end
|
||||
|
||||
it "does not add highlighting classes" do
|
||||
result = filter('<pre><code>This is a test</code></pre>')
|
||||
|
||||
expect(result.to_html).to eq('<pre><code>This is a test</code></pre>')
|
||||
end
|
||||
|
||||
include_examples "XSS prevention", "ruby"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,6 +24,11 @@ describe Gitlab::EncodingHelper do
|
|||
'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.',
|
||||
"mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'),
|
||||
"mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi "
|
||||
],
|
||||
[
|
||||
'string with detected encoding that is not supported in Ruby',
|
||||
"\xFFe,i\xFF,\xB8oi,'\xB8,\xFF,-",
|
||||
"--broken encoding: IBM420_ltr"
|
||||
]
|
||||
].each do |description, test_string, xpect|
|
||||
it description do
|
||||
|
|
|
@ -1590,7 +1590,7 @@ describe Ci::Build do
|
|||
|
||||
context 'when the branch is protected' do
|
||||
before do
|
||||
create(:protected_branch, project: build.project, name: build.ref)
|
||||
allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to include(protected_variable) }
|
||||
|
@ -1598,7 +1598,7 @@ describe Ci::Build do
|
|||
|
||||
context 'when the tag is protected' do
|
||||
before do
|
||||
create(:protected_tag, project: build.project, name: build.ref)
|
||||
allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to include(protected_variable) }
|
||||
|
@ -1635,7 +1635,7 @@ describe Ci::Build do
|
|||
|
||||
context 'when the branch is protected' do
|
||||
before do
|
||||
create(:protected_branch, project: build.project, name: build.ref)
|
||||
allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to include(protected_variable) }
|
||||
|
@ -1643,7 +1643,7 @@ describe Ci::Build do
|
|||
|
||||
context 'when the tag is protected' do
|
||||
before do
|
||||
create(:protected_tag, project: build.project, name: build.ref)
|
||||
allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to include(protected_variable) }
|
||||
|
|
|
@ -549,7 +549,7 @@ describe Group do
|
|||
|
||||
context 'when the ref is a protected branch' do
|
||||
before do
|
||||
create(:protected_branch, name: 'ref', project: project)
|
||||
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref is protected'
|
||||
|
@ -557,7 +557,7 @@ describe Group do
|
|||
|
||||
context 'when the ref is a protected tag' do
|
||||
before do
|
||||
create(:protected_tag, name: 'ref', project: project)
|
||||
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref is protected'
|
||||
|
@ -571,6 +571,10 @@ describe Group do
|
|||
let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) }
|
||||
let(:variable_child_3) { create(:ci_group_variable, group: group_child_3) }
|
||||
|
||||
before do
|
||||
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
||||
end
|
||||
|
||||
it 'returns all variables belong to the group and parent groups' do
|
||||
expected_array1 = [protected_variable, secret_variable]
|
||||
expected_array2 = [variable_child, variable_child_2, variable_child_3]
|
||||
|
|
|
@ -2092,7 +2092,7 @@ describe Project do
|
|||
|
||||
context 'when the ref is a protected branch' do
|
||||
before do
|
||||
create(:protected_branch, name: 'ref', project: project)
|
||||
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref is protected'
|
||||
|
@ -2100,7 +2100,7 @@ describe Project do
|
|||
|
||||
context 'when the ref is a protected tag' do
|
||||
before do
|
||||
create(:protected_tag, name: 'ref', project: project)
|
||||
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref is protected'
|
||||
|
@ -2125,6 +2125,8 @@ describe Project do
|
|||
|
||||
context 'when the ref is a protected branch' do
|
||||
before do
|
||||
allow(project).to receive(:repository).and_call_original
|
||||
allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(true)
|
||||
create(:protected_branch, name: 'ref', project: project)
|
||||
end
|
||||
|
||||
|
@ -2135,6 +2137,8 @@ describe Project do
|
|||
|
||||
context 'when the ref is a protected tag' do
|
||||
before do
|
||||
allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(false)
|
||||
allow(project).to receive_message_chain(:repository, :tag_exists?).and_return(true)
|
||||
create(:protected_tag, name: 'ref', project: project)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
|
||||
describe PersonalSnippetPolicy do
|
||||
let(:regular_user) { create(:user) }
|
||||
let(:external_user) { create(:user, :external) }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
|
||||
describe ProjectSnippetPolicy do
|
||||
let(:regular_user) { create(:user) }
|
||||
let(:external_user) { create(:user, :external) }
|
||||
|
|
|
@ -180,6 +180,18 @@ describe API::Search do
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
|
||||
end
|
||||
|
||||
context 'for milestones scope with group path as id' do
|
||||
before do
|
||||
another_project = create(:project, :public)
|
||||
create(:milestone, project: project, title: 'awesome milestone')
|
||||
create(:milestone, project: another_project, title: 'awesome milestone other project')
|
||||
|
||||
get api("/groups/#{CGI.escape(group.full_path)}/-/search", user), scope: 'milestones', search: 'awesome'
|
||||
end
|
||||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -286,6 +298,14 @@ describe API::Search do
|
|||
it_behaves_like 'response is correct', schema: 'public_api/v4/commits'
|
||||
end
|
||||
|
||||
context 'for commits scope with project path as id' do
|
||||
before do
|
||||
get api("/projects/#{CGI.escape(repo_project.full_path)}/-/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
|
||||
end
|
||||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/commits'
|
||||
end
|
||||
|
||||
context 'for blobs scope' do
|
||||
before do
|
||||
get api("/projects/#{repo_project.id}/-/search", user), scope: 'blobs', search: 'monitors'
|
||||
|
|
|
@ -32,6 +32,27 @@ describe API::Snippets do
|
|||
expect(json_response).to be_an Array
|
||||
expect(json_response.size).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns 404 for non-authenticated' do
|
||||
create(:personal_snippet, :internal)
|
||||
|
||||
get api("/snippets/")
|
||||
|
||||
expect(response).to have_gitlab_http_status(401)
|
||||
end
|
||||
|
||||
it 'does not return snippets related to a project with disable feature visibility' do
|
||||
project = create(:project)
|
||||
create(:project_member, project: project, user: user)
|
||||
public_snippet = create(:personal_snippet, :public, author: user, project: project)
|
||||
project.project_feature.update_attribute(:snippets_access_level, 0)
|
||||
|
||||
get api("/snippets/", user)
|
||||
|
||||
json_response.each do |snippet|
|
||||
expect(snippet["id"]).not_to eq(public_snippet.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /snippets/public' do
|
||||
|
|
|
@ -129,6 +129,12 @@ describe API::Todos do
|
|||
|
||||
post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
|
||||
end
|
||||
|
||||
it 'returns 404 if the todo does not belong to the current user' do
|
||||
post api("/todos/#{pending_1.id}/mark_as_done", author_1)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -38,6 +38,12 @@ describe API::V3::Todos do
|
|||
|
||||
delete v3_api("/todos/#{pending_1.id}", john_doe)
|
||||
end
|
||||
|
||||
it 'returns 404 if the todo does not belong to the current user' do
|
||||
delete v3_api("/todos/#{pending_1.id}", author_1)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ require 'spec_helper'
|
|||
|
||||
describe Search::SnippetService do
|
||||
let(:author) { create(:author) }
|
||||
let(:project) { create(:project) }
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') }
|
||||
let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') }
|
||||
|
|
|
@ -92,6 +92,7 @@ end
|
|||
|
||||
shared_examples 'a GitHub-ish import controller: POST create' do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:provider_username) { user.username }
|
||||
let(:provider_user) { OpenStruct.new(login: provider_username) }
|
||||
let(:provider_repo) do
|
||||
|
@ -107,14 +108,34 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
assign_session_token(provider)
|
||||
end
|
||||
|
||||
it 'returns 200 response when the project is imported successfully' do
|
||||
allow(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :json
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns 422 response when the project could not be imported' do
|
||||
allow(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: build(:project)))
|
||||
|
||||
post :create, format: :json
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
|
||||
context "when the repository owner is the provider user" do
|
||||
context "when the provider user and GitLab user's usernames match" do
|
||||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -124,9 +145,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -151,9 +172,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
it "takes the existing namespace" do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -163,9 +184,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -174,17 +195,17 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
context "when current user can create namespaces" do
|
||||
it "creates the namespace" do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).and_return(double(execute: true))
|
||||
.to receive(:new).and_return(double(execute: project))
|
||||
|
||||
expect { post :create, target_namespace: provider_repo.name, format: :js }.to change(Namespace, :count).by(1)
|
||||
expect { post :create, target_namespace: provider_repo.name, format: :json }.to change(Namespace, :count).by(1)
|
||||
end
|
||||
|
||||
it "takes the new namespace" do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, target_namespace: provider_repo.name, format: :js
|
||||
post :create, target_namespace: provider_repo.name, format: :json
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -195,17 +216,17 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
|
||||
it "doesn't create the namespace" do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).and_return(double(execute: true))
|
||||
.to receive(:new).and_return(double(execute: project))
|
||||
|
||||
expect { post :create, format: :js }.not_to change(Namespace, :count)
|
||||
expect { post :create, format: :json }.not_to change(Namespace, :count)
|
||||
end
|
||||
|
||||
it "takes the current user's namespace" do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, format: :js
|
||||
post :create, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -221,21 +242,21 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :json }
|
||||
end
|
||||
|
||||
it 'takes the selected name and default namespace' do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { new_name: test_name, format: :js }
|
||||
post :create, { new_name: test_name, format: :json }
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen an existing nested namespace and name for the project' do
|
||||
context 'user has chosen an existing nested namespace and name for the project', :postgresql do
|
||||
let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
|
||||
let(:test_name) { 'test_name' }
|
||||
|
@ -247,62 +268,123 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json }
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen a non-existent nested namespaces and name for the project' do
|
||||
context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
|
||||
let(:test_name) { 'test_name' }
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
|
||||
end
|
||||
|
||||
it 'creates the namespaces' do
|
||||
allow(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js } }
|
||||
expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } }
|
||||
.to change { Namespace.count }.by(2)
|
||||
end
|
||||
|
||||
it 'new namespace has the right parent' do
|
||||
allow(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
|
||||
|
||||
expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
|
||||
before do
|
||||
parent_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js }
|
||||
post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json }
|
||||
end
|
||||
|
||||
it 'creates the namespaces' do
|
||||
allow(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
|
||||
.and_return(double(execute: true))
|
||||
.and_return(double(execute: project))
|
||||
|
||||
expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } }
|
||||
.to change { Namespace.count }.by(2)
|
||||
end
|
||||
|
||||
it 'does not create a new namespace under the user namespace' do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: build_stubbed(:project)))
|
||||
|
||||
expect { post :create, { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name, format: :js } }
|
||||
.not_to change { Namespace.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'user cannot create a subgroup inside a group is not a member of' do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:parent_namespace) { create(:group, name: 'foo') }
|
||||
|
||||
it 'does not take the selected namespace and name' do
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
|
||||
.and_return(double(execute: build_stubbed(:project)))
|
||||
|
||||
post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js }
|
||||
end
|
||||
|
||||
it 'does not create the namespaces' do
|
||||
allow(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
|
||||
.and_return(double(execute: build_stubbed(:project)))
|
||||
|
||||
expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } }
|
||||
.to change { Namespace.count }.by(2)
|
||||
.not_to change { Namespace.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'user can use a group without having permissions to create a group' do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:group) { create(:group, name: 'foo') }
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
group.add_owner(user)
|
||||
user.update!(can_create_group: false)
|
||||
|
||||
expect(Gitlab::LegacyGithubImport::ProjectCreator)
|
||||
.to receive(:new).with(provider_repo, test_name, group, user, access_params, type: provider)
|
||||
.and_return(double(execute: build_stubbed(:project)))
|
||||
|
||||
post :create, { target_namespace: 'foo', new_name: test_name, format: :js }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can not create projects in the chosen namespace' do
|
||||
it 'returns 422 response' do
|
||||
other_namespace = create(:group, name: 'other_namespace')
|
||||
|
||||
post :create, { target_namespace: other_namespace.name, format: :json }
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,13 +41,13 @@ shared_examples 'variable list' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'adds new unprotected variable' do
|
||||
it 'adds new protected variable' do
|
||||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('key')
|
||||
find('.js-ci-variable-input-value').set('key value')
|
||||
find('.ci-variable-protected-item .js-project-feature-toggle').click
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
|
||||
end
|
||||
|
||||
click_button('Save variables')
|
||||
|
@ -59,7 +59,7 @@ shared_examples 'variable list' do
|
|||
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do
|
||||
expect(find('.js-ci-variable-input-key').value).to eq('key')
|
||||
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key value')
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -143,7 +143,6 @@ shared_examples 'variable list' do
|
|||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('unprotected_key')
|
||||
find('.js-ci-variable-input-value').set('unprotected_value')
|
||||
find('.ci-variable-protected-item .js-project-feature-toggle').click
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
|
||||
end
|
||||
|
@ -178,6 +177,7 @@ shared_examples 'variable list' do
|
|||
page.within('.js-ci-variable-list-section .js-row:last-child') do
|
||||
find('.js-ci-variable-input-key').set('protected_key')
|
||||
find('.js-ci-variable-input-value').set('protected_value')
|
||||
find('.ci-variable-protected-item .js-project-feature-toggle').click
|
||||
|
||||
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
|
||||
end
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
RSpec.shared_examples 'snippet visibility' do
|
||||
let!(:author) { create(:user) }
|
||||
let!(:member) { create(:user) }
|
||||
let!(:external) { create(:user, :external) }
|
||||
|
||||
let!(:snippet_type_visibilities) do
|
||||
{
|
||||
public: Snippet::PUBLIC,
|
||||
internal: Snippet::INTERNAL,
|
||||
private: Snippet::PRIVATE
|
||||
}
|
||||
end
|
||||
|
||||
context "For project snippets" do
|
||||
let!(:users) do
|
||||
{
|
||||
unauthenticated: nil,
|
||||
external: external,
|
||||
non_member: create(:user),
|
||||
member: member,
|
||||
author: author
|
||||
}
|
||||
end
|
||||
|
||||
let!(:project_type_visibilities) do
|
||||
{
|
||||
public: Gitlab::VisibilityLevel::PUBLIC,
|
||||
internal: Gitlab::VisibilityLevel::INTERNAL,
|
||||
private: Gitlab::VisibilityLevel::PRIVATE
|
||||
}
|
||||
end
|
||||
|
||||
let(:project_feature_visibilities) do
|
||||
{
|
||||
enabled: ProjectFeature::ENABLED,
|
||||
private: ProjectFeature::PRIVATE,
|
||||
disabled: ProjectFeature::DISABLED
|
||||
}
|
||||
end
|
||||
|
||||
where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do
|
||||
[
|
||||
# Public projects
|
||||
[:public, :enabled, :unauthenticated, :public, true],
|
||||
[:public, :enabled, :unauthenticated, :internal, false],
|
||||
[:public, :enabled, :unauthenticated, :private, false],
|
||||
|
||||
[:public, :enabled, :external, :public, true],
|
||||
[:public, :enabled, :external, :internal, false],
|
||||
[:public, :enabled, :external, :private, false],
|
||||
|
||||
[:public, :enabled, :non_member, :public, true],
|
||||
[:public, :enabled, :non_member, :internal, true],
|
||||
[:public, :enabled, :non_member, :private, false],
|
||||
|
||||
[:public, :enabled, :member, :public, true],
|
||||
[:public, :enabled, :member, :internal, true],
|
||||
[:public, :enabled, :member, :private, true],
|
||||
|
||||
[:public, :enabled, :author, :public, true],
|
||||
[:public, :enabled, :author, :internal, true],
|
||||
[:public, :enabled, :author, :private, true],
|
||||
|
||||
[:public, :private, :unauthenticated, :public, false],
|
||||
[:public, :private, :unauthenticated, :internal, false],
|
||||
[:public, :private, :unauthenticated, :private, false],
|
||||
|
||||
[:public, :private, :external, :public, false],
|
||||
[:public, :private, :external, :internal, false],
|
||||
[:public, :private, :external, :private, false],
|
||||
|
||||
[:public, :private, :non_member, :public, false],
|
||||
[:public, :private, :non_member, :internal, false],
|
||||
[:public, :private, :non_member, :private, false],
|
||||
|
||||
[:public, :private, :member, :public, true],
|
||||
[:public, :private, :member, :internal, true],
|
||||
[:public, :private, :member, :private, true],
|
||||
|
||||
[:public, :private, :author, :public, true],
|
||||
[:public, :private, :author, :internal, true],
|
||||
[:public, :private, :author, :private, true],
|
||||
|
||||
[:public, :disabled, :unauthenticated, :public, false],
|
||||
[:public, :disabled, :unauthenticated, :internal, false],
|
||||
[:public, :disabled, :unauthenticated, :private, false],
|
||||
|
||||
[:public, :disabled, :external, :public, false],
|
||||
[:public, :disabled, :external, :internal, false],
|
||||
[:public, :disabled, :external, :private, false],
|
||||
|
||||
[:public, :disabled, :non_member, :public, false],
|
||||
[:public, :disabled, :non_member, :internal, false],
|
||||
[:public, :disabled, :non_member, :private, false],
|
||||
|
||||
[:public, :disabled, :member, :public, false],
|
||||
[:public, :disabled, :member, :internal, false],
|
||||
[:public, :disabled, :member, :private, false],
|
||||
|
||||
[:public, :disabled, :author, :public, false],
|
||||
[:public, :disabled, :author, :internal, false],
|
||||
[:public, :disabled, :author, :private, false],
|
||||
|
||||
# Internal projects
|
||||
[:internal, :enabled, :unauthenticated, :public, false],
|
||||
[:internal, :enabled, :unauthenticated, :internal, false],
|
||||
[:internal, :enabled, :unauthenticated, :private, false],
|
||||
|
||||
[:internal, :enabled, :external, :public, false],
|
||||
[:internal, :enabled, :external, :internal, false],
|
||||
[:internal, :enabled, :external, :private, false],
|
||||
|
||||
[:internal, :enabled, :non_member, :public, true],
|
||||
[:internal, :enabled, :non_member, :internal, true],
|
||||
[:internal, :enabled, :non_member, :private, false],
|
||||
|
||||
[:internal, :enabled, :member, :public, true],
|
||||
[:internal, :enabled, :member, :internal, true],
|
||||
[:internal, :enabled, :member, :private, true],
|
||||
|
||||
[:internal, :enabled, :author, :public, true],
|
||||
[:internal, :enabled, :author, :internal, true],
|
||||
[:internal, :enabled, :author, :private, true],
|
||||
|
||||
[:internal, :private, :unauthenticated, :public, false],
|
||||
[:internal, :private, :unauthenticated, :internal, false],
|
||||
[:internal, :private, :unauthenticated, :private, false],
|
||||
|
||||
[:internal, :private, :external, :public, false],
|
||||
[:internal, :private, :external, :internal, false],
|
||||
[:internal, :private, :external, :private, false],
|
||||
|
||||
[:internal, :private, :non_member, :public, false],
|
||||
[:internal, :private, :non_member, :internal, false],
|
||||
[:internal, :private, :non_member, :private, false],
|
||||
|
||||
[:internal, :private, :member, :public, true],
|
||||
[:internal, :private, :member, :internal, true],
|
||||
[:internal, :private, :member, :private, true],
|
||||
|
||||
[:internal, :private, :author, :public, true],
|
||||
[:internal, :private, :author, :internal, true],
|
||||
[:internal, :private, :author, :private, true],
|
||||
|
||||
[:internal, :disabled, :unauthenticated, :public, false],
|
||||
[:internal, :disabled, :unauthenticated, :internal, false],
|
||||
[:internal, :disabled, :unauthenticated, :private, false],
|
||||
|
||||
[:internal, :disabled, :external, :public, false],
|
||||
[:internal, :disabled, :external, :internal, false],
|
||||
[:internal, :disabled, :external, :private, false],
|
||||
|
||||
[:internal, :disabled, :non_member, :public, false],
|
||||
[:internal, :disabled, :non_member, :internal, false],
|
||||
[:internal, :disabled, :non_member, :private, false],
|
||||
|
||||
[:internal, :disabled, :member, :public, false],
|
||||
[:internal, :disabled, :member, :internal, false],
|
||||
[:internal, :disabled, :member, :private, false],
|
||||
|
||||
[:internal, :disabled, :author, :public, false],
|
||||
[:internal, :disabled, :author, :internal, false],
|
||||
[:internal, :disabled, :author, :private, false],
|
||||
|
||||
# Private projects
|
||||
[:private, :enabled, :unauthenticated, :public, false],
|
||||
[:private, :enabled, :unauthenticated, :internal, false],
|
||||
[:private, :enabled, :unauthenticated, :private, false],
|
||||
|
||||
[:private, :enabled, :external, :public, true],
|
||||
[:private, :enabled, :external, :internal, true],
|
||||
[:private, :enabled, :external, :private, true],
|
||||
|
||||
[:private, :enabled, :non_member, :public, false],
|
||||
[:private, :enabled, :non_member, :internal, false],
|
||||
[:private, :enabled, :non_member, :private, false],
|
||||
|
||||
[:private, :enabled, :member, :public, true],
|
||||
[:private, :enabled, :member, :internal, true],
|
||||
[:private, :enabled, :member, :private, true],
|
||||
|
||||
[:private, :enabled, :author, :public, true],
|
||||
[:private, :enabled, :author, :internal, true],
|
||||
[:private, :enabled, :author, :private, true],
|
||||
|
||||
[:private, :private, :unauthenticated, :public, false],
|
||||
[:private, :private, :unauthenticated, :internal, false],
|
||||
[:private, :private, :unauthenticated, :private, false],
|
||||
|
||||
[:private, :private, :external, :public, true],
|
||||
[:private, :private, :external, :internal, true],
|
||||
[:private, :private, :external, :private, true],
|
||||
|
||||
[:private, :private, :non_member, :public, false],
|
||||
[:private, :private, :non_member, :internal, false],
|
||||
[:private, :private, :non_member, :private, false],
|
||||
|
||||
[:private, :private, :member, :public, true],
|
||||
[:private, :private, :member, :internal, true],
|
||||
[:private, :private, :member, :private, true],
|
||||
|
||||
[:private, :private, :author, :public, true],
|
||||
[:private, :private, :author, :internal, true],
|
||||
[:private, :private, :author, :private, true],
|
||||
|
||||
[:private, :disabled, :unauthenticated, :public, false],
|
||||
[:private, :disabled, :unauthenticated, :internal, false],
|
||||
[:private, :disabled, :unauthenticated, :private, false],
|
||||
|
||||
[:private, :disabled, :external, :public, false],
|
||||
[:private, :disabled, :external, :internal, false],
|
||||
[:private, :disabled, :external, :private, false],
|
||||
|
||||
[:private, :disabled, :non_member, :public, false],
|
||||
[:private, :disabled, :non_member, :internal, false],
|
||||
[:private, :disabled, :non_member, :private, false],
|
||||
|
||||
[:private, :disabled, :member, :public, false],
|
||||
[:private, :disabled, :member, :internal, false],
|
||||
[:private, :disabled, :member, :private, false],
|
||||
|
||||
[:private, :disabled, :author, :public, false],
|
||||
[:private, :disabled, :author, :internal, false],
|
||||
[:private, :disabled, :author, :private, false]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
let!(:project) { create(:project, visibility_level: project_type_visibilities[project_type]) }
|
||||
let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, project_feature_visibilities[feature_visibility]) }
|
||||
let!(:user) { users[user_type] }
|
||||
let!(:snippet) { create(:project_snippet, visibility_level: snippet_type_visibilities[snippet_type], project: project, author: author) }
|
||||
let!(:members) do
|
||||
project.add_developer(author)
|
||||
project.add_developer(member)
|
||||
project.add_developer(external) if project.private?
|
||||
end
|
||||
|
||||
context "For #{params[:project_type]} project and #{params[:user_type]} users" do
|
||||
it 'should agree with the read_project_snippet policy' do
|
||||
expect(can?(user, :read_project_snippet, snippet)).to eq(outcome)
|
||||
end
|
||||
|
||||
it 'should return proper outcome' do
|
||||
results = described_class.new(user, project: project).execute
|
||||
expect(results.include?(snippet)).to eq(outcome)
|
||||
end
|
||||
end
|
||||
|
||||
context "Without a given project and #{params[:user_type]} users" do
|
||||
it 'should return proper outcome' do
|
||||
results = described_class.new(user).execute
|
||||
expect(results.include?(snippet)).to eq(outcome)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'For personal snippets' do
|
||||
let!(:users) do
|
||||
{
|
||||
unauthenticated: nil,
|
||||
external: external,
|
||||
non_member: create(:user),
|
||||
author: author
|
||||
}
|
||||
end
|
||||
|
||||
where(:snippet_visibility, :user_type, :outcome) do
|
||||
[
|
||||
[:public, :unauthenticated, true],
|
||||
[:public, :external, true],
|
||||
[:public, :non_member, true],
|
||||
[:public, :author, true],
|
||||
|
||||
[:internal, :unauthenticated, false],
|
||||
[:internal, :external, false],
|
||||
[:internal, :non_member, true],
|
||||
[:internal, :author, true],
|
||||
|
||||
[:private, :unauthenticated, false],
|
||||
[:private, :external, false],
|
||||
[:private, :non_member, false],
|
||||
[:private, :author, true]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
let!(:user) { users[user_type] }
|
||||
let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) }
|
||||
|
||||
context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do
|
||||
it 'should agree with read_personal_snippet policy' do
|
||||
expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome)
|
||||
end
|
||||
|
||||
it 'should return proper outcome' do
|
||||
results = described_class.new(user).execute
|
||||
expect(results.include?(snippet)).to eq(outcome)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
* waitForImages 1.4
|
||||
* -----------------
|
||||
* Provides a callback when all images have loaded in your given selector.
|
||||
* http://www.alexanderdickson.com/
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2011 Alex Dickson
|
||||
* Licensed under the MIT licenses.
|
||||
* See website for more info.
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
// Namespace all events.
|
||||
var eventNamespace = 'waitForImages';
|
||||
|
||||
// CSS properties which contain references to images.
|
||||
$.waitForImages = {
|
||||
hasImageProperties: [
|
||||
'backgroundImage',
|
||||
'listStyleImage',
|
||||
'borderImage',
|
||||
'borderCornerImage'
|
||||
]
|
||||
};
|
||||
|
||||
// Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded.
|
||||
$.expr[':'].uncached = function(obj) {
|
||||
// Ensure we are dealing with an `img` element with a valid `src` attribute.
|
||||
if ( ! $(obj).is('img[src!=""]')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Firefox's `complete` property will always be`true` even if the image has not been downloaded.
|
||||
// Doing it this way works in Firefox.
|
||||
var img = document.createElement('img');
|
||||
img.src = obj.src;
|
||||
return ! img.complete;
|
||||
};
|
||||
|
||||
$.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) {
|
||||
|
||||
// Handle options object.
|
||||
if ($.isPlainObject(arguments[0])) {
|
||||
eachCallback = finishedCallback.each;
|
||||
waitForAll = finishedCallback.waitForAll;
|
||||
finishedCallback = finishedCallback.finished;
|
||||
}
|
||||
|
||||
// Handle missing callbacks.
|
||||
finishedCallback = finishedCallback || $.noop;
|
||||
eachCallback = eachCallback || $.noop;
|
||||
|
||||
// Convert waitForAll to Boolean
|
||||
waitForAll = !! waitForAll;
|
||||
|
||||
// Ensure callbacks are functions.
|
||||
if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) {
|
||||
throw new TypeError('An invalid callback was supplied.');
|
||||
};
|
||||
|
||||
return this.each(function() {
|
||||
// Build a list of all imgs, dependent on what images will be considered.
|
||||
var obj = $(this),
|
||||
allImgs = [];
|
||||
|
||||
if (waitForAll) {
|
||||
// CSS properties which may contain an image.
|
||||
var hasImgProperties = $.waitForImages.hasImageProperties || [],
|
||||
matchUrl = /url\((['"]?)(.*?)\1\)/g;
|
||||
|
||||
// Get all elements, as any one of them could have a background image.
|
||||
obj.find('*').each(function() {
|
||||
var element = $(this);
|
||||
|
||||
// If an `img` element, add it. But keep iterating in case it has a background image too.
|
||||
if (element.is('img:uncached')) {
|
||||
allImgs.push({
|
||||
src: element.attr('src'),
|
||||
element: element[0]
|
||||
});
|
||||
}
|
||||
|
||||
$.each(hasImgProperties, function(i, property) {
|
||||
var propertyValue = element.css(property);
|
||||
// If it doesn't contain this property, skip.
|
||||
if ( ! propertyValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get all url() of this element.
|
||||
var match;
|
||||
while (match = matchUrl.exec(propertyValue)) {
|
||||
allImgs.push({
|
||||
src: match[2],
|
||||
element: element[0]
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// For images only, the task is simpler.
|
||||
obj
|
||||
.find('img:uncached')
|
||||
.each(function() {
|
||||
allImgs.push({
|
||||
src: this.src,
|
||||
element: this
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var allImgsLength = allImgs.length,
|
||||
allImgsLoaded = 0;
|
||||
|
||||
// If no images found, don't bother.
|
||||
if (allImgsLength == 0) {
|
||||
finishedCallback.call(obj[0]);
|
||||
};
|
||||
|
||||
$.each(allImgs, function(i, img) {
|
||||
|
||||
var image = new Image;
|
||||
|
||||
// Handle the image loading and error with the same callback.
|
||||
$(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) {
|
||||
allImgsLoaded++;
|
||||
|
||||
// If an error occurred with loading the image, set the third argument accordingly.
|
||||
eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load');
|
||||
|
||||
if (allImgsLoaded == allImgsLength) {
|
||||
finishedCallback.call(obj[0]);
|
||||
return false;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
image.src = img.src;
|
||||
});
|
||||
});
|
||||
};
|
||||
})(jQuery);
|
|
@ -4203,6 +4203,10 @@ jquery-ujs@1.2.2:
|
|||
dependencies:
|
||||
jquery ">=1.8.0"
|
||||
|
||||
jquery.waitforimages@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/jquery.waitforimages/-/jquery.waitforimages-2.2.0.tgz#63f23131055a1b060dc913e6d874bcc9b9e6b16b"
|
||||
|
||||
"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-2.2.4.tgz#2c89d6889b5eac522a7eea32c14521559c6cbf02"
|
||||
|
|
Loading…
Reference in New Issue