Merge branch 'master' into backstage/gb/build-stages-catch-up-migration

* master: (26 commits)
This commit is contained in:
Grzegorz Bizon 2018-02-12 13:09:53 +01:00
commit 14a9c951cc
87 changed files with 1356 additions and 772 deletions

View File

@ -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

View File

@ -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;

View File

@ -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';

View File

@ -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

View File

@ -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,
};

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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() {

View File

@ -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>

View File

@ -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>
`,
};

View File

@ -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>

View File

@ -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';

View File

@ -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>

View File

@ -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";

View File

@ -121,6 +121,10 @@
width: 100%;
text-align: left;
}
.environment-child-row {
padding-left: 20px;
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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}"

View File

@ -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

View File

@ -0,0 +1,3 @@
class ProjectSerializer < BaseSerializer
entity ProjectEntity
end

View File

@ -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

View File

@ -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]"

View File

@ -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(',')))}")

View File

@ -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')

View File

@ -0,0 +1,5 @@
---
title: Adds tooltip in environment names to increase readability
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Update vue component naming guidelines
merge_request: 17018
author: George Tsiolis
type: other

View File

@ -0,0 +1,5 @@
---
title: Move IssuableTimeTracker vue component
merge_request: 16948
author: George Tsiolis
type: performance

View File

@ -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,
};
```

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>)

View File

@ -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

View File

@ -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')"

View File

@ -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",

View File

@ -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/

View File

@ -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

View File

@ -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

View File

@ -16,7 +16,7 @@ module QA
end
def sandbox_name
'gitlab-qa-sandbox'
Runtime::Env.sandbox_name || 'gitlab-qa-sandbox'
end
end
end

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);
});
});
});

View File

@ -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(`

View File

@ -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();

View File

@ -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&lt;e&gt;&lt;/e&gt;ds escap&quot;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);
});
});
});
});

View File

@ -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>&lt;script&gt;alert(1)&lt;/script&gt;</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

View File

@ -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

View File

@ -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) }

View File

@ -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]

View File

@ -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

View File

@ -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) }

View File

@ -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) }

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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') }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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"