Commit e7238677 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent c2b98d3d
......@@ -2,6 +2,7 @@ extends:
- '@gitlab'
- plugin:promise/recommended
- plugin:no-jquery/slim
- plugin:no-jquery/deprecated-3.4
globals:
__webpack_public_path__: true
gl: false
......@@ -48,8 +49,10 @@ rules:
no-jquery/no-animate: off
# all offenses of no-jquery/no-animate-toggle are false positives ( $toast.show() )
no-jquery/no-animate-toggle: off
no-jquery/no-event-shorthand: off
no-jquery/no-fade: off
no-jquery/no-serialize: error
no-jquery/no-sizzle: off
promise/always-return: off
promise/no-callback-in-promise: off
overrides:
......
......@@ -220,9 +220,6 @@ export default {
this.assignedDiscussions = false;
this.fetchData(false);
},
isLatestVersion() {
return window.location.search.indexOf('diff_id') === -1;
},
startDiffRendering() {
requestIdleCallback(
() => {
......@@ -232,7 +229,7 @@ export default {
);
},
fetchData(toggleTree = true) {
if (this.isLatestVersion() && this.glFeatures.diffsBatchLoad) {
if (this.glFeatures.diffsBatchLoad) {
this.fetchDiffFilesMeta()
.then(() => {
if (toggleTree) this.hideTreeListIfJustOneFile();
......
......@@ -90,14 +90,13 @@ export const fetchDiffFiles = ({ state, commit }) => {
};
export const fetchDiffFilesBatch = ({ commit, state }) => {
const baseUrl = `${state.endpointBatch}?per_page=${DIFFS_PER_PAGE}`;
const url = page => (page ? `${baseUrl}&page=${page}` : baseUrl);
commit(types.SET_BATCH_LOADING, true);
const getBatch = page =>
axios
.get(url(page))
.get(state.endpointBatch, {
params: { page, per_page: DIFFS_PER_PAGE, w: state.showWhitespace ? '0' : '1' },
})
.then(({ data: { pagination, diff_files } }) => {
commit(types.SET_DIFF_DATA_BATCH, { diff_files });
commit(types.SET_BATCH_LOADING, false);
......
......@@ -115,5 +115,30 @@ export const isOnDefaultBranch = (_state, getters) =>
export const canPushToBranch = (_state, getters) =>
getters.currentBranch && getters.currentBranch.can_push;
export const isFileDeletedAndReadded = (state, getters) => path => {
const stagedFile = getters.getStagedFile(path);
const file = state.entries[path];
return Boolean(stagedFile && stagedFile.deleted && file.tempFile);
};
// checks if any diff exists in the staged or unstaged changes for this path
export const getDiffInfo = (state, getters) => path => {
const stagedFile = getters.getStagedFile(path);
const file = state.entries[path];
const renamed = file.prevPath ? file.path !== file.prevPath : false;
const deletedAndReadded = getters.isFileDeletedAndReadded(path);
const deleted = deletedAndReadded ? false : file.deleted;
const tempFile = deletedAndReadded ? false : file.tempFile;
const changed = file.content !== (deletedAndReadded ? stagedFile.raw : file.raw);
return {
exists: changed || renamed || deleted || tempFile,
changed,
renamed,
deleted,
tempFile,
};
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -3,11 +3,16 @@ import ZenMode from '~/zen_mode';
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import snippetEmbed from '~/snippet/snippet_embed';
import initSnippetsApp from '~/snippets';
document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
if (!gon.features.snippetsVue) {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
} else {
initSnippetsApp();
}
});
......@@ -104,10 +104,10 @@ export default {
return acc.concat({
name,
path,
to: `/tree/${this.ref}${path}`,
to: `/-/tree/${this.ref}${path}`,
});
},
[{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }],
[{ name: this.projectShortPath, path: '/', to: `/-/tree/${this.ref}/` }],
);
},
canCreateMrFromFork() {
......
......@@ -15,7 +15,7 @@ export default {
const splitArray = this.path.split('/');
splitArray.pop();
return { path: `/tree/${this.commitRef}/${splitArray.join('/')}` };
return { path: `/-/tree/${this.commitRef}/${splitArray.join('/')}` };
},
},
methods: {
......
......@@ -84,7 +84,7 @@ export default {
},
computed: {
routerLinkTo() {
return this.isFolder ? { path: `/tree/${this.ref}/${this.path}` } : null;
return this.isFolder ? { path: `/-/tree/${this.ref}/${this.path}` } : null;
},
iconName() {
return `fa-${getIconName(this.type, this.path)}`;
......
......@@ -12,7 +12,7 @@ export default function createRouter(base, baseRef) {
base: joinPaths(gon.relative_url_root || '', base),
routes: [
{
path: `/tree/${baseRef}(/.*)?`,
path: `/-/tree/${baseRef}(/.*)?`,
name: 'treePath',
component: TreePage,
props: route => ({
......
<script>
import getSnippet from '../queries/getSnippet.query.graphql';
import GetSnippetQuery from '../queries/snippet.query.graphql';
import SnippetHeader from './snippet_header.vue';
import { GlLoadingIcon } from '@gitlab/ui';
export default {
components: {
SnippetHeader,
GlLoadingIcon,
},
apollo: {
snippetData: {
query: getSnippet,
snippet: {
query: GetSnippetQuery,
variables() {
return {
ids: this.snippetGid,
......@@ -21,11 +27,24 @@ export default {
},
data() {
return {
snippetData: {},
snippet: {},
};
},
computed: {
isLoading() {
return this.$apollo.queries.snippet.loading;
},
},
};
</script>
<template>
<div class="js-snippet-view"></div>
<div class="js-snippet-view">
<gl-loading-icon
v-if="isLoading"
:label="__('Loading snippet')"
:size="2"
class="loading-animation prepend-top-20 append-bottom-20"
/>
<snippet-header v-else :snippet="snippet" />
</div>
</template>
<script>
import { __ } from '~/locale';
import {
GlAvatar,
GlIcon,
GlSprintf,
GlButton,
GlModal,
GlAlert,
GlLoadingIcon,
GlDropdown,
GlDropdownItem,
} from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql';
import CanCreatePersonalSnippet from '../queries/userPermissions.query.graphql';
import CanCreateProjectSnippet from '../queries/projectPermissions.query.graphql';
export default {
components: {
GlAvatar,
GlIcon,
GlSprintf,
GlButton,
GlModal,
GlAlert,
GlLoadingIcon,
GlDropdown,
GlDropdownItem,
TimeAgoTooltip,
},
apollo: {
canCreateSnippet: {
query() {
return this.snippet.project ? CanCreateProjectSnippet : CanCreatePersonalSnippet;
},
variables() {
return {
fullPath: this.snippet.project ? this.snippet.project.fullPath : undefined,
};
},
update(data) {
return this.snippet.project
? data.project.userPermissions.createSnippet
: data.currentUser.userPermissions.createSnippet;
},
},
},
props: {
snippet: {
type: Object,
required: true,
},
},
data() {
return {
isDeleting: false,
errorMessage: '',
canCreateSnippet: false,
};
},
computed: {
personalSnippetActions() {
return [
{
condition: this.snippet.userPermissions.updateSnippet,
text: __('Edit'),
href: this.editLink,
click: undefined,
variant: 'outline-info',
cssClass: undefined,
},
{
condition: this.snippet.userPermissions.adminSnippet,
text: __('Delete'),
href: undefined,
click: this.showDeleteModal,
variant: 'outline-danger',
cssClass: 'btn-inverted btn-danger ml-2',
},
{
condition: this.canCreateSnippet,
text: __('New snippet'),
href: this.snippet.project
? `${this.snippet.project.webUrl}/snippets/new`
: '/snippets/new',
click: undefined,
variant: 'outline-success',
cssClass: 'btn-inverted btn-success ml-2',
},
];
},
editLink() {
return `${this.snippet.webUrl}/edit`;
},
visibility() {
return this.snippet.visibilityLevel;
},
snippetVisibilityLevelDescription() {
switch (this.visibility) {
case 'private':
return this.snippet.project !== null
? __('The snippet is visible only to project members.')
: __('The snippet is visible only to me.');
case 'internal':
return __('The snippet is visible to any logged in user.');
default:
return __('The snippet can be accessed without any authentication.');
}
},
visibilityLevelIcon() {
switch (this.visibility) {
case 'private':
return 'lock';
case 'internal':
return 'shield';
default:
return 'earth';
}
},
},
methods: {
redirectToSnippets() {
window.location.pathname = 'dashboard/snippets';
},
closeDeleteModal() {
this.$refs.deleteModal.hide();
},
showDeleteModal() {
this.$refs.deleteModal.show();
},
deleteSnippet() {
this.isDeleting = true;
this.$apollo
.mutate({
mutation: DeleteSnippetMutation,
variables: { id: this.snippet.id },
})
.then(() => {
this.isDeleting = false;
this.errorMessage = undefined;
this.closeDeleteModal();
this.redirectToSnippets();
})
.catch(err => {
this.isDeleting = false;
this.errorMessage = err.message;
});
},
},
};
</script>
<template>
<div class="detail-page-header">
<div class="detail-page-header-body">
<div
class="snippet-box qa-snippet-box has-tooltip d-flex align-items-center append-right-5 mb-1"
:title="snippetVisibilityLevelDescription"
data-container="body"
>
<span class="sr-only">
{{ s__(`VisibilityLevel|${visibility}`) }}
</span>
<gl-icon :name="visibilityLevelIcon" :size="14" />
</div>
<div class="creator">
<gl-sprintf message="Authored %{timeago} by %{author}">
<template #timeago>
<time-ago-tooltip
:time="snippet.createdAt"
tooltip-placement="bottom"
css-class="snippet_updated_ago"
/>
</template>
<template #author>
<a :href="snippet.author.webUrl" class="d-inline">
<gl-avatar :size="24" :src="snippet.author.avatarUrl" />
<span class="bold">{{ snippet.author.name }}</span>
</a>
</template>
</gl-sprintf>
</div>
</div>
<div class="detail-page-header-actions">
<div class="d-none d-sm-block">
<template v-for="(action, index) in personalSnippetActions">
<gl-button
v-if="action.condition"
:key="index"
:variant="action.variant"
:class="action.cssClass"
:href="action.href || undefined"
@click="action.click ? action.click() : undefined"
>
{{ action.text }}
</gl-button>
</template>
</div>
<div class="d-block d-sm-none dropdown">
<gl-dropdown :text="__('Options')" class="w-100" toggle-class="text-center">
<gl-dropdown-item
v-for="(action, index) in personalSnippetActions"
:key="index"
:href="action.href || undefined"
@click="action.click ? action.click() : undefined"
>{{ action.text }}</gl-dropdown-item
>
</gl-dropdown>
</div>
</div>
<gl-modal ref="deleteModal" modal-id="delete-modal" title="Example title">
<template #modal-title>{{ __('Delete snippet?') }}</template>
<gl-alert v-if="errorMessage" variant="danger" class="mb-2" @dismiss="errorMessage = ''">{{
errorMessage
}}</gl-alert>
<gl-sprintf message="Are you sure you want to delete %{name}?">
<template #name
><strong>{{ snippet.title }}</strong></template
>
</gl-sprintf>
<template #modal-footer>
<gl-button @click="closeDeleteModal">{{ __('Cancel') }}</gl-button>
<gl-button
variant="danger"
:disabled="isDeleting"
data-qa-selector="delete_snippet_button"
@click="deleteSnippet"
>
<gl-loading-icon v-if="isDeleting" inline />
{{ __('Delete snippet') }}
</gl-button>
</template>
</gl-modal>
</div>
</template>
fragment Author on Snippet {
author {
name,
avatarUrl,
username,
webUrl
}
}
\ No newline at end of file
fragment Project on Snippet {
project {
fullPath
webUrl
}
}
\ No newline at end of file
fragment SnippetBase on Snippet {
id
title
description
createdAt
updatedAt
visibilityLevel
webUrl
userPermissions {
adminSnippet
updateSnippet
}
}
\ No newline at end of file
mutation DeleteSnippet($id: ID!) {
destroySnippet(input: {id: $id}) {
errors
}
}
\ No newline at end of file
query getSnippet($ids: [ID!]) {
snippets(ids: $ids) {
edges {
node {
title
description
createdAt
updatedAt
visibility
}
}
}
}
query CanCreateProjectSnippet($fullPath: ID!) {
project(fullPath: $fullPath) {
userPermissions {
createSnippet
}
}
}
\ No newline at end of file
#import '../fragments/snippetBase.fragment.graphql'
#import '../fragments/project.fragment.graphql'
#import '../fragments/author.fragment.graphql'
query GetSnippetQuery($ids: [ID!]) {
snippets(ids: $ids) {
edges {
node {
...SnippetBase
...Project
...Author
}
}
}
}
query CanCreatePersonalSnippet {
currentUser {
userPermissions {
createSnippet
}
}
}
\ No newline at end of file
......@@ -79,10 +79,10 @@ export default {
return this.projectPath.indexOf('/') === 0 ? '' : `${gon.relative_url_root}/`;
},
fullOldPath() {
return `${this.basePath}${this.projectPath}/raw/${this.oldSha}/${this.oldPath}`;
return `${this.basePath}${this.projectPath}/-/raw/${this.oldSha}/${this.oldPath}`;
},
fullNewPath() {
return `${this.basePath}${this.projectPath}/raw/${this.newSha}/${this.newPath}`;
return `${this.basePath}${this.projectPath}/-/raw/${this.newSha}/${this.newPath}`;
},
},
};
......
......@@ -40,12 +40,12 @@
// Classes using mixins coming from @gitlab-ui
// can be removed once https://gitlab.com/gitlab-org/gitlab/merge_requests/19021 has been merged
.gl-bg-blue-500 { @include gl-bg-blue-500; }
.gl-bg-red-100 { @include gl-bg-red-100; }
.gl-bg-orange-100 { @include gl-bg-orange-100; }
.gl-bg-gray-100 { @include gl-bg-gray-100; }
.gl-bg-green-100 { @include gl-bg-green-100;}
.gl-text-blue-500 { @include gl-text-blue-500; }
.gl-text-gray-900 { @include gl-text-gray-900; }
.gl-text-red-700 { @include gl-text-red-700; }
.gl-text-orange-700 { @include gl-text-orange-700; }
......
......@@ -43,6 +43,8 @@ module SubmoduleHelper
elsif github_dot_com_url?(url)
standard_links('github.com', namespace, project, submodule_item_id)
elsif gitlab_dot_com_url?(url)
# This need to be replaced with /-/tree routing once one is landed on
# GitLab.com. Issue https://gitlab.com/gitlab-org/gitlab/issues/42764
standard_links('gitlab.com', namespace, project, submodule_item_id)
else
[sanitize_submodule_url(url), nil]
......
......@@ -38,13 +38,13 @@ module TreeHelper
# many paths, as with a repository tree that has thousands of items.
def fast_project_blob_path(project, blob_path)
ActionDispatch::Journey::Router::Utils.escape_path(
File.join(relative_url_root, project.path_with_namespace, 'blob', blob_path)
File.join(relative_url_root, project.path_with_namespace, '-', 'blob', blob_path)
)
end
def fast_project_tree_path(project, tree_path)
ActionDispatch::Journey::Router::Utils.escape_path(
File.join(relative_url_root, project.path_with_namespace, 'tree', tree_path)
File.join(relative_url_root, project.path_with_namespace, '-', 'tree', tree_path)
)
end
......
......@@ -3,7 +3,7 @@
module Clusters
module Applications
class Knative < ApplicationRecord
VERSION = '0.7.0'
VERSION = '0.9.0'
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'
METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'
FETCH_IP_ADDRESS_DELAY = 30.seconds
......
......@@ -43,7 +43,7 @@
= f.check_box :external do
External
%p.light
External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups.
External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects, groups, or personal snippets.
%row.hidden#warning_external_automatically_set.hidden
.badge.badge-warning.text-white
= _('Automatically marked as default internal user')
......@@ -3,7 +3,8 @@
- if current_user && current_user.snippets.any? || @snippets.any?
.page-title-controls
= link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet")
- if can?(current_user, :create_personal_snippet)
= link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet")
.top-area
%ul.nav-links.nav.nav-tabs
......
- @hide_top_links = true
- page_title "Snippets"
- header_title "Snippets", dashboard_snippets_path
- button_path = new_snippet_path if can?(current_user, :create_personal_snippet)
= render 'dashboard/snippets_head'
- if current_user.snippets.exists?
......@@ -9,4 +10,4 @@
- if current_user.snippets.exists?
= render partial: 'shared/snippets/list', locals: { link_project: true }
- else
= render 'shared/empty_states/snippets', button_path: new_snippet_path
= render 'shared/empty_states/snippets', button_path: button_path
......@@ -16,6 +16,6 @@
%a.provider-btn
= s_('Profiles|Active')
- elsif link_allowed
= link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn gl-bg-blue-500' do
= link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn gl-text-blue-500' do
= s_('Profiles|Connect')
= render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: group_saml_identities
......@@ -8,8 +8,7 @@
- if can?(current_user, :create_project_snippet, @project)
.nav-controls
- if can?(current_user, :create_project_snippet, @project)
= link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
= link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
= render 'shared/snippets/list'
- else
......
......@@ -3,13 +3,16 @@
- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= render 'shared/snippets/header'
- if Feature.enabled?(:snippets_vue)
#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id} }
- else
= render 'shared/snippets/header'
.project-snippets
%article.file-holder.snippet-file-content
= render 'shared/snippets/blob'
.project-snippets
%article.file-holder.snippet-file-content
= render 'shared/snippets/blob'
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
#notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true
#notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true
......@@ -11,7 +11,8 @@
%p
= s_('SnippetsEmptyState|They can be either public or private.')
.text-center
= link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn btn-success', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link'
- if button_path
= link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn btn-success', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link'
- unless current_page?(dashboard_snippets_path)
= link_to s_('SnippetsEmptyState|Explore public snippets'), explore_snippets_path, class: 'btn btn-default', title: s_('SnippetsEmptyState|Explore public snippets')
- else
......
......@@ -3,7 +3,7 @@
- current_user_empty_message_header = s_('UserProfile|You haven\'t created any snippets.')
- current_user_empty_message_description = s_('UserProfile|Snippets in GitLab can either be private, internal, or public.')
- primary_button_label = _('New snippet')
- primary_button_link = new_snippet_path
- primary_button_link = new_snippet_path if can?(current_user, :create_personal_snippet)
- visitor_empty_message = s_('UserProfile|No snippets found.')
.snippets-list-holder
......
......@@ -21,6 +21,7 @@ const plugins = [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-json-strings',
'@babel/plugin-proposal-private-methods',
'@babel/plugin-proposal-optional-chaining',
];
// add code coverage tooling if necessary
......
---
title: "!21542 Part 1: Add new utils for Web IDE store"
merge_request: 21673
author:
type: fixed
---
title: Match external user new snippet button visibility to permissions
merge_request: 21718
author:
type: fixed
---
title: Move repository routes under - scope
merge_request: 20455
author:
type: deprecated
---
title: Update Knative to 0.9.0
merge_request: 21361
author: cab105
type: added
---
title: Added migration which adds service desk username column
merge_request: 21733
author:
type: added
......@@ -209,6 +209,6 @@ panel_groups:
weight: 1
metrics:
- id: system_metrics_knative_function_invocation_count
query_range: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_app=~"%{function_name}.*"}[1m])*60))'
query_range: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_service=~"%{function_name}.*"}[1m])*60))'
label: invocations / minute
unit: requests
......@@ -554,7 +554,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
:forks, :group_links, :import, :avatar, :mirror,
:cycle_analytics, :mattermost, :variables, :triggers,
:environments, :protected_environments, :error_tracking,
:serverless, :clusters, :audit_events, :wikis, :merge_requests)
:serverless, :clusters, :audit_events, :wikis, :merge_requests,
:blob, :tree, :raw, :blame, :commits, :create_dir, :find_file, :files)
end
# rubocop: disable Cop/PutProjectRoutesUnderScope
......
......@@ -65,7 +65,7 @@ scope format: false do
resources :protected_tags, only: [:index, :show, :create, :update, :destroy]
end
scope constraints: { id: /[^\0]+/ } do
scope path: '-', constraints: { id: /[^\0]+/ } do
scope controller: :blob do
get '/new/*id', action: :new, as: :new_blob
post '/create/*id', action: :create, as: :create_blob
......
# frozen_string_literal: true
class AddServiceDeskUsername < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :service_desk_settings, :outgoing_name, :string, limit: 255
end
end
# frozen_string_literal: true
class Knative09PrometheusUpdate < ActiveRecord::Migration[5.2]
DOWNTIME = false
def up
::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
# no-op
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_12_16_183532) do
ActiveRecord::Schema.define(version: 2019_12_17_160632) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -3663,6 +3663,7 @@ ActiveRecord::Schema.define(version: 2019_12_16_183532) do
create_table "service_desk_settings", primary_key: "project_id", id: :bigint, default: nil, force: :cascade do |t|
t.string "issue_template_key", limit: 255
t.string "outgoing_name", limit: 255
end
create_table "services", id: :serial, force: :cascade do |t|
......
......@@ -10,11 +10,6 @@ migrations automatically reschedule themselves for a later point in time.
## When To Use Background Migrations
> **Note:**
> When adding background migrations _you must_ make sure they are announced in the
> monthly release post along with an estimate of how long it will take to complete
> the migrations.
In the vast majority of cases you will want to use a regular Rails migration
instead. Background migrations should be used when migrating _data_ in
tables that have so many rows this process would take hours when performed in a
......@@ -34,6 +29,11 @@ Some examples where background migrations can be useful:
- Populating one column based on JSON stored in another column.
- Migrating data that depends on the output of external services (e.g. an API).
> **Note:**
> If the background migration is part of an important upgrade, make sure it's announced
> in the release post. Discuss with your Project Manager if you're not sure the migration falls
> into this category.
## Isolation
Background migrations must be isolated and can not use application code (e.g.
......
......@@ -39,43 +39,31 @@ To distinguish queries from mutations and fragments, the following naming conven
- `addUser.mutation.graphql` for mutations;
- `basicUser.fragment.graphql` for fragments.
GraphQL:
- Queries are stored in `(ee/)app/assets/javascripts/` under the feature. For example, `respository/queries`. Frontend components can use these stored queries.
- Mutations are stored in
`(ee/)app/assets/javascripts/<subfolders>/<name of mutation>.mutation.graphql`.
### Fragments
Fragments are a way to make your complex GraphQL queries more readable and re-usable.
They can be stored in a separate file and imported.
Fragments are a way to make your complex GraphQL queries more readable and re-usable. Here is an example of GraphQL fragment:
For example, a fragment that references another fragment:
```ruby
fragment BaseEpic on Epic {
```javascript
fragment DesignListItem on Design {
id
iid
title
webPath
relativePosition
userPermissions {
adminEpic
createEpic
}
image
event
filename
notesCount
}
```
fragment EpicNode on Epic {
...BaseEpic
state
reference(full: true)
relationPath
createdAt
closedAt
hasChildren
hasIssues
group {
fullPath
Fragments can be stored in separate files, imported and used in queries, mutations or other fragments.
```javascript
#import "./designList.fragment.graphql"
#import "./diffRefs.fragment.graphql"
fragment DesignItem on Design {
...DesignListItem
fullPath
diffRefs {
...DesignDiffRefs
}
}
```
......
......@@ -253,7 +253,7 @@ project and should only have access to that project.
External users:
- Cannot create groups or projects.
- Cannot create groups, projects, or personal snippets.
- Can only access projects to which they are explicitly granted access,
thus hiding all other internal or private ones from them (like being
logged out).
......
......@@ -152,11 +152,18 @@ module Banzai
def rebuild_relative_uri(uri)
file_path = nested_file_path_if_exists(uri)
resource_type = uri_type(file_path)
# Repository routes are under /-/ scope now.
# Since we craft a path without using route helpers we must
# ensure - is added here.
prefix = '-' if %w(tree blob raw commits).include?(resource_type.to_s)
uri.path = [
relative_url_root,
project.full_path,
uri_type(file_path),
prefix,
resource_type,
Addressable::URI.escape(ref).gsub('#', '%23'),
Addressable::URI.escape(file_path)
].compact.join('/').squeeze('/').chomp('/')
......
......@@ -12,10 +12,12 @@ module Gitlab
def link_dependencies
link_json('ImportPath') do |path|
case path
when %r{\A(?<repo>github\.com/#{REPO_REGEX})/(?<path>.+)\z}
"https://#{$~[:repo]}/tree/master/#{$~[:path]}"
when %r{\A(?<repo>gitlab\.com/#{NESTED_REPO_REGEX})\.git/(?<path>.+)\z},
%r{\A(?<repo>git(lab|hub)\.com/#{REPO_REGEX})/(?<path>.+)\z}
%r{\A(?<repo>gitlab\.com/#{REPO_REGEX})/(?<path>.+)\z}
"https://#{$~[:repo]}/tree/master/#{$~[:path]}"
"https://#{$~[:repo]}/-/tree/master/#{$~[:path]}"
when /\Agolang\.org/
"https://godoc.org/#{path}"
else
......
......@@ -220,6 +220,8 @@ excluded_attributes:
- :encrypted_token
- :encrypted_token_iv
- :enabled
service_desk_setting:
- :outgoing_name
methods:
notes:
......
......@@ -60,7 +60,7 @@ module Gitlab
end
meta_import_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{repository_url}"
meta_source_tag = tag :meta, name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/tree/#{branch}{/dir} #{project_url}/blob/#{branch}{/dir}/{file}#L{line}"
meta_source_tag = tag :meta, name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}"
head_tag = content_tag :head, meta_import_tag + meta_source_tag
html_tag = content_tag :html, head_tag + body_tag
[html_tag, 200]
......
......@@ -5,6 +5,10 @@ module Quality
UnknownTestLevelError = Class.new(StandardError)
TEST_LEVEL_FOLDERS = {
migration: %w[
migrations
lib/gitlab/background_migration
],
unit: %w[
bin
config
......@@ -19,7 +23,6 @@ module Quality
initializers
javascripts
lib
migrations
models
policies
presenters
......@@ -36,10 +39,6 @@ module Quality
workers
elastic_integration
],
migration: %w[
migrations
lib/gitlab/background_migration
],
integration: %w[
controllers
mailers
......
......@@ -4994,12 +4994,18 @@ msgstr ""
msgid "Could not create group"
msgstr ""
msgid "Could not create issue"
msgstr ""
msgid "Could not create project"
msgstr ""
msgid "Could not delete chat nickname %{chat_name}."
msgstr ""
msgid "Could not fetch projects"
msgstr ""
msgid "Could not remove the trigger."
msgstr ""
......@@ -5644,6 +5650,12 @@ msgstr ""
msgid "Delete list"
msgstr ""
msgid "Delete snippet"
msgstr ""
msgid "Delete snippet?"
msgstr ""
msgid "Delete source branch"
msgstr ""
......@@ -10001,6 +10013,9 @@ msgstr ""
msgid "IssuesAnalytics|Total:"
msgstr ""
msgid "Issue|Title"
msgstr ""
msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
msgstr ""
......@@ -10709,6 +10724,9 @@ msgstr ""
msgid "Loading issues"
msgstr ""
msgid "Loading snippet"
msgstr ""
msgid "Loading the GitLab IDE..."
msgstr ""
......@@ -11724,6 +11742,9 @@ msgstr ""
msgid "New issue"
msgstr ""
msgid "New issue title"
msgstr ""
msgid "New label"
msgstr ""
......
......@@ -44,7 +44,7 @@ gitlab:
memory: 37.5M
maxReplicas: 3
hpa:
targetAverageValue: 130m
targetAverageValue: 500m
deployment:
livenessProbe:
timeoutSeconds: 5
......@@ -56,6 +56,8 @@ gitlab:
limits:
cpu: 975m
memory: 1450M
hpa:
targetAverageValue: 650m
task-runner:
resources:
requests:
......
......@@ -72,7 +72,7 @@ describe MetricsDashboard do
it 'includes project_blob_path only for project dashboards' do
expect(system_dashboard['project_blob_path']).to be_nil
expect(project_dashboard['project_blob_path']).to eq("/#{project.namespace.path}/#{project.name}/blob/master/.gitlab/dashboards/test.yml")
expect(project_dashboard['project_blob_path']).to eq("/#{project.namespace.path}/#{project.name}/-/blob/master/.gitlab/dashboards/test.yml")
end
describe 'project permissions' do
......
......@@ -36,7 +36,7 @@ describe Projects::BlameController do
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/master")
.to redirect_to("/#{project.full_path}/-/tree/master")
end
end
......
......@@ -33,7 +33,7 @@ describe Projects::BlobController do
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/master")
.to redirect_to("/#{project.full_path}/-/tree/master")
end
end
......@@ -115,7 +115,7 @@ describe Projects::BlobController do
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/markdown/doc")
.to redirect_to("/#{project.full_path}/-/tree/markdown/doc")
end
end
end
......
......@@ -37,7 +37,7 @@ describe Projects::BranchesController do
let(:ref) { "master" }
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/merge_branch")
.to redirect_to("/#{project.full_path}/-/tree/merge_branch")
end
end
......@@ -46,7 +46,7 @@ describe Projects::BranchesController do
let(:ref) { "master" }
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/alert('merge');")
.to redirect_to("/#{project.full_path}/-/tree/alert('merge');")
end
end
......@@ -88,7 +88,7 @@ describe Projects::BranchesController do
}
expect(subject)
.to redirect_to("/#{project.full_path}/tree/1-feature-branch")
.to redirect_to("/#{project.full_path}/-/tree/1-feature-branch")
end
it 'posts a system note' do
......
......@@ -69,7 +69,7 @@ describe Projects::RawController do
env: :raw_blob_request_limit,
remote_ip: '0.0.0.0',
request_method: 'GET',
path: "/#{project.full_path}/raw/#{file_path}"
path: "/#{project.full_path}/-/raw/#{file_path}"
}
expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
......
......@@ -149,6 +149,14 @@ describe Projects::Serverless::FunctionsController do
include_examples 'GET #show with valid data'
end
context 'on Knative 0.9.0' do
before do
prepare_knative_stubs(knative_09_service(knative_stub_options))
end
include_examples 'GET #show with valid data'
end
end
end
......@@ -210,6 +218,14 @@ describe Projects::Serverless::FunctionsController do
include_examples 'GET #index with data'
end
context 'on Knative 0.9.0' do
before do
prepare_knative_stubs(knative_09_service(knative_stub_options))
end
include_examples 'GET #index with data'
end
end
def prepare_knative_stubs(knative_service)
......
......@@ -45,7 +45,7 @@ describe Projects::TreeController do
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/master")
.to redirect_to("/#{project.full_path}/-/tree/master")
end
end
......@@ -60,7 +60,7 @@ describe Projects::TreeController do
it 'redirects' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/empty-branch")
.to redirect_to("/#{project.full_path}/-/tree/empty-branch")
end
end
......@@ -125,7 +125,7 @@ describe Projects::TreeController do
let(:id) { 'master/README.md' }
it 'redirects' do
redirect_url = "/#{project.full_path}/blob/master/README.md"
redirect_url = "/#{project.full_path}/-/blob/master/README.md"
expect(subject)
.to redirect_to(redirect_url)
end
......@@ -153,7 +153,7 @@ describe Projects::TreeController do
it 'redirects to the new directory' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/#{branch_name}/#{path}")
.to redirect_to("/#{project.full_path}/-/tree/#{branch_name}/#{path}")
expect(flash[:notice]).to eq('The directory has been successfully created.')
end
end
......@@ -164,7 +164,7 @@ describe Projects::TreeController do
it 'does not allow overwriting of existing files' do
expect(subject)
.to redirect_to("/#{project.full_path}/tree/master")
.to redirect_to("/#{project.full_path}/-/tree/master")
expect(flash[:alert]).to eq('A file with this name already exists')
end
end
......
......@@ -943,7 +943,7 @@ describe ProjectsController do
end
it 'renders JSON body with image links expanded' do
expanded_path = "/#{project_with_repo.full_path}/raw/master/files/images/logo-white.png"
expanded_path = "/#{project_with_repo.full_path}/-/raw/master/files/images/logo-white.png"
post :preview_markdown, params: preview_markdown_params
......
......@@ -6,6 +6,7 @@ describe 'Dashboard snippets' do
context 'when the project has snippets' do
let(:project) { create(:project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
sign_in(project.owner)
......@@ -13,10 +14,16 @@ describe 'Dashboard snippets' do
end
it_behaves_like 'paginated snippets'
it 'shows new snippet button in header' do
parent_element = page.find('.page-title-controls')
expect(parent_element).to have_link('New snippet')
end
end
context 'when there are no project snippets', :js do
let(:project) { create(:project, :public) }
before do
sign_in(project.owner)
visit dashboard_snippets_path
......@@ -28,6 +35,11 @@ describe 'Dashboard snippets' do
expect(element).to have_content("Snippets are small pieces of code or notes that you want to keep.")
expect(element.find('.svg-content img')['src']).to have_content('illustrations/snippets_empty')
end
it 'shows new snippet button in main content area' do
parent_element = page.find('.row.empty-state')
expect(parent_element).to have_link('New snippet')
end
end
context 'filtering by visibility' do
......@@ -76,4 +88,26 @@ describe 'Dashboard snippets' do
expect(page).to have_content(snippets[0].title)
end
end
context 'as an external user' do
let(:user) { create(:user, :external) }
before do
sign_in(user)
visit dashboard_snippets_path
end
context 'without snippets' do
it 'hides new snippet button' do
expect(page).not_to have_link('New snippet')
end
end
context 'with snippets' do
let!(:snippets) { create(:personal_snippet, author: user) }
it 'hides new snippet button' do
expect(page).not_to have_link('New snippet')
end
end
end
end
......@@ -8,6 +8,7 @@ describe 'Thread Comments Snippet', :js do
let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
before do
stub_feature_flags(snippets_vue: false)
project.add_maintainer(user)
sign_in(user)
......
......@@ -70,6 +70,7 @@ describe 'issue move to another project' do
context 'user does not have permission to move the issue to a project', :js do
let!(:private_project) { create(:project, :private) }
let(:another_project) { create(:project) }
before do
another_project.add_guest(user)
end
......
......@@ -6,6 +6,7 @@ describe "User views issues" do
let!(:closed_issue) { create(:closed_issue, project: project) }
let!(:open_issue1) { create(:issue, project: project) }
let!(:open_issue2) { create(:issue, project: project) }
set(:user) { create(:user) }
shared_examples "opens issue from list" do
......
......@@ -9,6 +9,7 @@ describe 'User sorts merge requests' do
let!(:merge_request2) do
create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
end
set(:user) { create(:user) }
set(:group) { create(:group) }
set(:group_member) { create(:group_member, :maintainer, user: user, group: group) }
......
......@@ -113,6 +113,7 @@ describe 'User views open merge requests' do
context 'when project is internal' do
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
set(:project) { create(:project, :internal, :repository) }
context 'when signed in' do
......
......@@ -33,6 +33,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
let(:remember_me) { false }
let(:user) { create(:omniauth_user, extern_uid: uid, provider: provider.to_s) }
let(:two_factor_user) { create(:omniauth_user, :two_factor, extern_uid: uid, provider: provider.to_s) }
provider == :salesforce ? let(:additional_info) { { extra: { email_verified: true } } } : let(:additional_info) { {} }
before do
......
......@@ -34,6 +34,7 @@ describe 'Member autocomplete', :js do
context 'adding a new note on a Issue' do
let(:noteable) { create(:issue, author: author, project: project) }
before do
visit project_issue_path(project, noteable)
end
......@@ -47,6 +48,7 @@ describe 'Member autocomplete', :js do
create(:merge_request, source_project: project,
target_project: project, author: author)
end
before do
visit project_merge_request_path(project, noteable)
end
......
......@@ -194,6 +194,7 @@ describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do
context 'when third party offers are disabled' do
let(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
......
......@@ -193,6 +193,7 @@ describe 'Projects > Files > User edits files', :js do
context 'when the user already had a fork of the project', :js do
let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) }
before do
visit(project2_tree_path_root_ref)
wait_for_requests
......
......@@ -18,6 +18,7 @@ describe 'Projects > Snippets > Create Snippet', :js do
context 'when a user is authenticated' do
before do
stub_feature_flags(snippets_vue: false)
project.add_maintainer(user)
sign_in(user)
......@@ -76,6 +77,10 @@ describe 'Projects > Snippets > Create Snippet', :js do
end
context 'when a user is not authenticated' do
before do
stub_feature_flags(snippets_vue: false)
end
it 'shows a public snippet on the index page but not the New snippet button' do
snippet = create(:project_snippet, :public, project: project)
......
......@@ -8,6 +8,7 @@ describe 'Projects > Snippets > Project snippet', :js do
let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) }
before do
stub_feature_flags(snippets_vue: false)
project.add_maintainer(user)
sign_in(user)
end
......
......@@ -8,6 +8,7 @@ describe 'Projects > Snippets > User comments on a snippet', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(snippets_vue: false)
project.add_maintainer(user)
sign_in(user)
......
......@@ -8,6 +8,7 @@ describe 'Projects > Snippets > User deletes a snippet' do
let(:user) { create(:user) }
before do
stub_feature_flags(snippets_vue: false)
project.add_maintainer(user)
sign_in(user)
......
......@@ -8,6 +8,7 @@ describe 'Projects > Snippets > User updates a snippet' do
let(:user) { create(:user) }
before do
stub_feature_flags(snippets_vue: false)
project.add_maintainer(user)
sign_in(user)
......
......@@ -7,6 +7,7 @@ describe 'Reportable note on snippets', :js do
let(:project) { create(:project) }
before do
stub_feature_flags(snippets_vue: false)
project.add_maintainer(user)
sign_in(user)
end
......
......@@ -53,6 +53,7 @@ describe 'Internal Group access' do
describe 'GET /groups/:path/merge_requests' do
let(:project) { create(:project, :internal, :repository, group: group) }
subject { merge_requests_group_path(group) }
it { is_expected.to be_allowed_for(:admin) }
......
......@@ -53,6 +53,7 @@ describe 'Private Group access' do
describe 'GET /groups/:path/merge_requests' do
let(:project) { create(:project, :private, :repository, group: group) }
subject { merge_requests_group_path(group) }
it { is_expected.to be_allowed_for(:admin) }
......
......@@ -53,6 +53,7 @@ describe 'Public Group access' do
describe 'GET /groups/:path/merge_requests' do
let(:project) { create(:project, :public, :repository, group: group) }
subject { merge_requests_group_path(group) }
it { is_expected.to be_allowed_for(:admin) }
......
......@@ -129,6 +129,7 @@ describe "Internal Project Access" do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -186,6 +187,7 @@ describe "Internal Project Access" do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -327,6 +329,7 @@ describe "Internal Project Access" do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -379,6 +382,7 @@ describe "Internal Project Access" do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
subject { project_job_path(project, build.id) }
context "when allowed for public and internal" do
......@@ -417,6 +421,7 @@ describe "Internal Project Access" do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
subject { trace_project_job_path(project, build.id) }
context 'when allowed for public and internal' do
......@@ -482,6 +487,7 @@ describe "Internal Project Access" do
describe "GET /:project_path/-/environments/:id" do
let(:environment) { create(:environment, project: project) }
subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -497,6 +503,7 @@ describe "Internal Project Access" do
describe "GET /:project_path/-/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
......
......@@ -129,6 +129,7 @@ describe "Private Project Access" do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -186,6 +187,7 @@ describe "Private Project Access" do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -311,6 +313,7 @@ describe "Private Project Access" do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -365,6 +368,7 @@ describe "Private Project Access" do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
subject { project_job_path(project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -398,6 +402,7 @@ describe "Private Project Access" do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
subject { trace_project_job_path(project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -443,6 +448,7 @@ describe "Private Project Access" do
describe "GET /:project_path/-/environments/:id" do
let(:environment) { create(:environment, project: project) }
subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -458,6 +464,7 @@ describe "Private Project Access" do
describe "GET /:project_path/-/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
......
......@@ -143,6 +143,7 @@ describe "Public Project Access" do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -195,6 +196,7 @@ describe "Public Project Access" do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
subject { project_job_path(project, build.id) }
context "when allowed for public" do
......@@ -233,6 +235,7 @@ describe "Public Project Access" do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
subject { trace_project_job_path(project, build.id) }
context 'when allowed for public' do
......@@ -298,6 +301,7 @@ describe "Public Project Access" do
describe "GET /:project_path/-/environments/:id" do
let(:environment) { create(:environment, project: project) }
subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -313,6 +317,7 @@ describe "Public Project Access" do
describe "GET /:project_path/-/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
......@@ -399,6 +404,7 @@ describe "Public Project Access" do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
......
......@@ -6,30 +6,59 @@ describe 'Explore Snippets' do
let!(:public_snippet) { create(:personal_snippet, :public) }
let!(:internal_snippet) { create(:personal_snippet, :internal) }
let!(:private_snippet) { create(:personal_snippet, :private) }
let(:user) { nil }
it 'User should see snippets that are not private' do
sign_in create(:user)
before do
sign_in(user) if user
visit explore_snippets_path
expect(page).to have_content(public_snippet.title)
expect(page).to have_content(internal_snippet.title)
expect(page).not_to have_content(private_snippet.title)
end
it 'External user should see only public snippets' do
sign_in create(:user, :external)
visit explore_snippets_path
context 'User' do
let(:user) { create(:user) }
it 'see snippets that are not private' do
expect(page).to have_content(public_snippet.title)
expect(page).to have_content(internal_snippet.title)
expect(page).not_to have_content(private_snippet.title)
end
expect(page).to have_content(public_snippet.title)
expect(page).not_to have_content(internal_snippet.title)
expect(page).not_to have_content(private_snippet.title)
it 'shows new snippet button in header' do
parent_element = page.find('.page-title-controls')
expect(parent_element).to have_link('New snippet')
end
end
it 'Not authenticated user should see only public snippets' do
visit explore_snippets_path
context 'External user' do
let(:user) { create(:user, :external) }
it 'see only public snippets' do
expect(page).to have_content(public_snippet.title)
expect(page).not_to have_content(internal_snippet.title)
expect(page).not_to have_content(private_snippet.title)
end
context 'without snippets' do
before do
Snippet.delete_all
end
it 'hides new snippet button' do
expect(page).not_to have_link('New snippet')
end
end
context 'with snippets' do
it 'hides new snippet button' do
expect(page).not_to have_link('New snippet')
end
end
end
expect(page).to have_content(public_snippet.title)
expect(page).not_to have_content(internal_snippet.title)
expect(page).not_to have_content(private_snippet.title)
context 'Not authenticated user' do
it 'see only public snippets' do
expect(page).to have_content(public_snippet.title)
expect(page).not_to have_content(internal_snippet.title)
expect(page).not_to have_content(private_snippet.title)
end
end
end
......@@ -146,6 +146,7 @@ describe 'Task Lists' do
describe 'for Notes' do
let!(:issue) { create(:issue, author: user, project: project) }
describe 'multiple tasks' do
let!(:note) do
create(:note, note: markdown, noteable: issue,
......
......@@ -406,6 +406,7 @@ describe 'Login' do
describe 'with required two-factor authentication enabled' do
let(:user) { create(:user) }
# TODO: otp_grace_period_started_at
context 'global setting' do
......
......@@ -4,6 +4,7 @@ require 'spec_helper'
describe ClustersFinder do
let(:project) { create(:project) }
set(:user) { create(:user) }
describe '#execute' do
......
......@@ -6,6 +6,7 @@ describe GroupDescendantsFinder do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:params) { {} }
subject(:finder) do
described_class.new(current_user: user, parent_group: group, params: params)
end
......
......@@ -132,11 +132,13 @@ describe GroupProjectsFinder do
context "only shared" do
let(:options) { { only_shared: true } }
it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) }
end
context "only owned" do
let(:options) { { only_owned: true } }
it { is_expected.to eq([private_project, public_project]) }
end
......
......@@ -111,6 +111,7 @@ describe GroupsFinder do
context 'authorized to private project' do
context 'project one level deep' do
let!(:subproject) { create(:project, :private, namespace: private_subgroup) }
before do
subproject.add_guest(user)
end
......@@ -129,6 +130,7 @@ describe GroupsFinder do
context 'project two levels deep' do
let!(:private_subsubgroup) { create(:group, :private, parent: private_subgroup) }
let!(:subsubproject) { create(:project, :private, namespace: private_subsubgroup) }
before do
subsubproject.add_guest(user)
end
......
......@@ -786,6 +786,7 @@ describe IssuesFinder do
describe '#with_confidentiality_access_check' do
let(:guest) { create(:user) }
set(:authorized_user) { create(:user) }
set(:project) { create(:project, namespace: authorized_user.namespace) }
set(:public_issue) { create(:issue, project: project) }
......
......@@ -6,6 +6,7 @@ describe MergeRequestTargetProjectFinder do
include ProjectForksHelper
let(:user) { create(:user) }
subject(:finder) { described_class.new(current_user: user, source_project: forked_project) }
shared_examples 'finding related projects' do
......
......@@ -6,6 +6,7 @@ describe PipelinesFinder do
let(:project) { create(:project, :public, :repository) }
let(:current_user) { nil }
let(:params) { {} }
subject { described_class.new(project, current_user, params).execute }
describe "#execute" do
......
......@@ -181,6 +181,7 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
describe 'filter by non_public' do
let(:params) { { non_public: true } }
before do
private_project.add_developer(current_user)
end
......@@ -190,6 +191,7 @@ describe ProjectsFinder, :do_not_mock_admin_mode do
describe 'filter by starred' do
let(:params) { { starred: true } }
before do
current_user.toggle_star(public_project)
end
......
......@@ -96,6 +96,7 @@ describe TagsFinder do
context 'filter and sort' do
let(:tags_to_compare) { %w[v1.0.0 v1.1.0] }
subject { described_class.new(repository, params).execute.select { |tag| tags_to_compare.include?(tag.name) } }
context 'when sort by updated_desc' do
......
......@@ -99,6 +99,15 @@
"access_level": 50,
"notification_level": 3
}
},
"_links": {
"self": "https://gitlab.com/api/v4/projects/278964",
"issues": "https://gitlab.com/api/v4/projects/278964/issues",
"merge_requests": "https://gitlab.com/api/v4/projects/278964/merge_requests",
"repo_branches": "https://gitlab.com/api/v4/projects/278964/repository/branches",
"labels": "https://gitlab.com/api/v4/projects/278964/labels",
"events": "https://gitlab.com/api/v4/projects/278964/events",
"members": "https://gitlab.com/api/v4/projects/278964/members"
}
}, {
"id": 7,
......
import './get_client_rects';
import './inner_text';
// workaround for JSDOM not supporting innerText
// see https://github.com/jsdom/jsdom/issues/1245
Object.defineProperty(global.Element.prototype, 'innerText', {
get() {
return this.textContent;
},
set(value) {
this.textContext = value;
},
configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch
});
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment