Skip to content
Snippets Groups Projects
Commit 6d31b8f0 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 69944ffb
No related branches found
No related tags found
No related merge requests found
Showing
with 281 additions and 173 deletions
Loading
Loading
@@ -113,10 +113,9 @@ const Api = {
.get(url, {
params: Object.assign(defaults, options),
})
.then(({ data }) => {
.then(({ data, headers }) => {
callback(data);
return data;
return { data, headers };
});
},
 
Loading
Loading
Loading
Loading
@@ -47,7 +47,8 @@ export default {
hasSearchQuery: true,
});
},
[types.RECEIVE_SEARCHED_ITEMS_SUCCESS](state, rawItems) {
[types.RECEIVE_SEARCHED_ITEMS_SUCCESS](state, results) {
const rawItems = results.data;
Object.assign(state, {
items: rawItems.map(rawItem => ({
id: rawItem.id,
Loading
Loading
Loading
Loading
@@ -11,11 +11,35 @@ export default {
computed: {
...mapState(['traceEndpoint', 'trace', 'isTraceComplete']),
},
updated() {
this.$nextTick(() => {
this.handleScrollDown();
});
},
mounted() {
this.$nextTick(() => {
this.handleScrollDown();
});
},
methods: {
...mapActions(['toggleCollapsibleLine']),
...mapActions(['toggleCollapsibleLine', 'scrollBottom']),
handleOnClickCollapsibleLine(section) {
this.toggleCollapsibleLine(section);
},
/**
* The job log is sent in HTML, which means we need to use `v-html` to render it
* Using the updated hook with $nextTick is not enough to wait for the DOM to be updated
* in this case because it runs before `v-html` has finished running, since there's no
* Vue binding.
* In order to scroll the page down after `v-html` has finished, we need to use setTimeout
*/
handleScrollDown() {
if (this.isScrolledToBottomBeforeReceivingTrace) {
setTimeout(() => {
this.scrollBottom();
}, 0);
}
},
},
};
</script>
Loading
Loading
<script>
import { mapActions } from 'vuex';
import _ from 'underscore';
import { s__, __, sprintf } from '~/locale';
import { truncateSha } from '~/lib/utils/text_utility';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteEditedText from './note_edited_text.vue';
import noteHeader from './note_header.vue';
export default {
name: 'DiffDiscussionHeader',
components: {
userAvatarLink,
noteEditedText,
noteHeader,
},
props: {
discussion: {
type: Object,
required: true,
},
},
computed: {
notes() {
return this.discussion.notes;
},
firstNote() {
return this.notes[0];
},
lastNote() {
return this.notes[this.notes.length - 1];
},
author() {
return this.firstNote.author;
},
resolvedText() {
return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
},
lastUpdatedBy() {
return this.notes.length > 1 ? this.lastNote.author : null;
},
lastUpdatedAt() {
return this.notes.length > 1 ? this.lastNote.created_at : null;
},
headerText() {
const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`;
const linkEnd = '</a>';
const { commit_id: commitId } = this.discussion;
let commitDisplay = commitId;
if (commitId) {
commitDisplay = `<span class="commit-sha">${truncateSha(commitId)}</span>`;
}
const {
for_commit: isForCommit,
diff_discussion: isDiffDiscussion,
active: isActive,
} = this.discussion;
let text = s__('MergeRequests|started a thread');
if (isForCommit) {
text = s__(
'MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}',
);
} else if (isDiffDiscussion && commitId) {
text = isActive
? s__('MergeRequests|started a thread on commit %{linkStart}%{commitDisplay}%{linkEnd}')
: s__(
'MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitDisplay}%{linkEnd}',
);
} else if (isDiffDiscussion) {
text = isActive
? s__('MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}')
: s__(
'MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}',
);
}
return sprintf(text, { commitDisplay, linkStart, linkEnd }, false);
},
},
methods: {
...mapActions(['toggleDiscussion']),
toggleDiscussionHandler() {
this.toggleDiscussion({ discussionId: this.discussion.id });
},
},
};
</script>
<template>
<div class="discussion-header note-wrapper">
<div v-once class="timeline-icon align-self-start flex-shrink-0">
<user-avatar-link
v-if="author"
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="author.name"
:img-size="40"
/>
</div>
<div class="timeline-content w-100">
<note-header
:author="author"
:created-at="firstNote.created_at"
:note-id="firstNote.id"
:include-toggle="true"
:expanded="discussion.expanded"
@toggleHandler="toggleDiscussionHandler"
>
<span v-html="headerText"></span>
</note-header>
<note-edited-text
v-if="discussion.resolved"
:edited-at="discussion.resolved_at"
:edited-by="discussion.resolved_by"
:action-text="resolvedText"
class-name="discussion-headline-light js-discussion-headline"
/>
<note-edited-text
v-else-if="lastUpdatedAt"
:edited-at="lastUpdatedAt"
:edited-by="lastUpdatedBy"
:action-text="__('Last updated')"
class-name="discussion-headline-light js-discussion-headline"
/>
</div>
</div>
</template>
<script>
import _ from 'underscore';
import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { s__, __, sprintf } from '~/locale';
import { s__, __ } from '~/locale';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import icon from '~/vue_shared/components/icon.vue';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
import diffDiscussionHeader from './diff_discussion_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue';
import noteForm from './note_form.vue';
import diffWithNote from './diff_with_note.vue';
import noteable from '../mixins/noteable';
Loading
Loading
@@ -27,9 +24,8 @@ export default {
components: {
icon,
userAvatarLink,
noteHeader,
diffDiscussionHeader,
noteSignedOutWidget,
noteEditedText,
noteForm,
DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
TimelineEntryItem,
Loading
Loading
@@ -92,9 +88,6 @@ export default {
currentUser() {
return this.getUserData;
},
author() {
return this.firstNote.author;
},
autosaveKey() {
return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
},
Loading
Loading
@@ -104,27 +97,6 @@ export default {
firstNote() {
return this.discussion.notes.slice(0, 1)[0];
},
lastUpdatedBy() {
const { notes } = this.discussion;
if (notes.length > 1) {
return notes[notes.length - 1].author;
}
return null;
},
lastUpdatedAt() {
const { notes } = this.discussion;
if (notes.length > 1) {
return notes[notes.length - 1].created_at;
}
return null;
},
resolvedText() {
return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
},
shouldShowJumpToNextDiscussion() {
return this.showJumpToNextDiscussion(this.discussionsByDiffOrder ? 'diff' : 'discussion');
},
Loading
Loading
@@ -150,40 +122,6 @@ export default {
shouldHideDiscussionBody() {
return this.shouldRenderDiffs && !this.isExpanded;
},
actionText() {
const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`;
const linkEnd = '</a>';
let { commit_id: commitId } = this.discussion;
if (commitId) {
commitId = `<span class="commit-sha">${truncateSha(commitId)}</span>`;
}
const {
for_commit: isForCommit,
diff_discussion: isDiffDiscussion,
active: isActive,
} = this.discussion;
let text = s__('MergeRequests|started a thread');
if (isForCommit) {
text = s__('MergeRequests|started a thread on commit %{linkStart}%{commitId}%{linkEnd}');
} else if (isDiffDiscussion && commitId) {
text = isActive
? s__('MergeRequests|started a thread on commit %{linkStart}%{commitId}%{linkEnd}')
: s__(
'MergeRequests|started a thread on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}',
);
} else if (isDiffDiscussion) {
text = isActive
? s__('MergeRequests|started a thread on %{linkStart}the diff%{linkEnd}')
: s__(
'MergeRequests|started a thread on %{linkStart}an old version of the diff%{linkEnd}',
);
}
return sprintf(text, { commitId, linkStart, linkEnd }, false);
},
diffLine() {
if (this.line) {
return this.line;
Loading
Loading
@@ -208,16 +146,11 @@ export default {
methods: {
...mapActions([
'saveNote',
'toggleDiscussion',
'removePlaceholderNotes',
'toggleResolveNote',
'expandDiscussion',
'removeConvertedDiscussion',
]),
truncateSha,
toggleDiscussionHandler() {
this.toggleDiscussion({ discussionId: this.discussion.id });
},
showReplyForm() {
this.isReplying = true;
},
Loading
Loading
@@ -311,43 +244,7 @@ export default {
class="discussion js-discussion-container"
data-qa-selector="discussion_content"
>
<div v-if="shouldRenderDiffs" class="discussion-header note-wrapper">
<div v-once class="timeline-icon align-self-start flex-shrink-0">
<user-avatar-link
v-if="author"
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="author.name"
:img-size="40"
/>
</div>
<div class="timeline-content w-100">
<note-header
:author="author"
:created-at="firstNote.created_at"
:note-id="firstNote.id"
:include-toggle="true"
:expanded="discussion.expanded"
@toggleHandler="toggleDiscussionHandler"
>
<span v-html="actionText"></span>
</note-header>
<note-edited-text
v-if="discussion.resolved"
:edited-at="discussion.resolved_at"
:edited-by="discussion.resolved_by"
:action-text="resolvedText"
class-name="discussion-headline-light js-discussion-headline"
/>
<note-edited-text
v-else-if="lastUpdatedAt"
:edited-at="lastUpdatedAt"
:edited-by="lastUpdatedBy"
action-text="Last updated"
class-name="discussion-headline-light js-discussion-headline"
/>
</div>
</div>
<diff-discussion-header v-if="shouldRenderDiffs" :discussion="discussion" />
<div v-if="!shouldHideDiscussionBody" class="discussion-body">
<component
:is="wrapperComponent"
Loading
Loading
<script>
import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import _ from 'underscore';
import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
Loading
Loading
@@ -23,6 +24,7 @@ export default {
'markdownDocsPath',
'markdownPreviewPath',
'releasesPagePath',
'updateReleaseApiDocsPath',
]),
showForm() {
return !this.isFetchingRelease && !this.fetchError;
Loading
Loading
@@ -42,6 +44,20 @@ export default {
tagName() {
return this.$store.state.release.tagName;
},
tagNameHintText() {
return sprintf(
__(
'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}',
),
{
linkStart: `<a href="${_.escape(
this.updateReleaseApiDocsPath,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
},
false,
);
},
releaseTitle: {
get() {
return this.$store.state.release.name;
Loading
Loading
@@ -77,22 +93,22 @@ export default {
<div class="d-flex flex-column">
<p class="pt-3 js-subtitle-text" v-html="subtitleText"></p>
<form v-if="showForm" @submit.prevent="updateRelease()">
<div class="row">
<gl-form-group class="col-md-6 col-lg-5 col-xl-4">
<label for="git-ref">{{ __('Tag name') }}</label>
<gl-form-input
id="git-ref"
v-model="tagName"
type="text"
class="form-control"
aria-describedby="tag-name-help"
disabled
/>
<div id="tag-name-help" class="form-text text-muted">
{{ __('Choose an existing tag, or create a new one') }}
<gl-form-group>
<div class="row">
<div class="col-md-6 col-lg-5 col-xl-4">
<label for="git-ref">{{ __('Tag name') }}</label>
<gl-form-input
id="git-ref"
v-model="tagName"
type="text"
class="form-control"
aria-describedby="tag-name-help"
disabled
/>
</div>
</gl-form-group>
</div>
</div>
<div id="tag-name-help" class="form-text text-muted" v-html="tagNameHintText"></div>
</gl-form-group>
<gl-form-group>
<label for="release-title">{{ __('Release title') }}</label>
<gl-form-input
Loading
Loading
Loading
Loading
@@ -4,6 +4,7 @@ export default () => ({
releasesPagePath: null,
markdownDocsPath: null,
markdownPreviewPath: null,
updateReleaseApiDocsPath: null,
 
release: null,
 
Loading
Loading
Loading
Loading
@@ -16,7 +16,6 @@ export default function setupVueRepositoryList() {
const { dataset } = el;
const { projectPath, projectShortPath, ref, fullName } = dataset;
const router = createRouter(projectPath, ref);
const hideOnRootEls = document.querySelectorAll('.js-hide-on-root');
 
apolloProvider.clients.defaultClient.cache.writeData({
data: {
Loading
Loading
@@ -28,20 +27,7 @@ export default function setupVueRepositoryList() {
});
 
router.afterEach(({ params: { pathMatch } }) => {
const isRoot = pathMatch === undefined || pathMatch === '/';
setTitle(pathMatch, ref, fullName);
if (!isRoot) {
document
.querySelectorAll('.js-keep-hidden-on-navigation')
.forEach(elem => elem.classList.add('hidden'));
}
document
.querySelectorAll('.js-hide-on-navigation')
.forEach(elem => elem.classList.toggle('hidden', !isRoot));
hideOnRootEls.forEach(elem => elem.classList.toggle('hidden', isRoot));
});
 
const breadcrumbEl = document.getElementById('js-repo-breadcrumb');
Loading
Loading
<script>
import TreeContent from '../components/tree_content.vue';
import TreePage from './tree.vue';
import { updateElementsVisibility } from '../utils/dom';
 
export default {
components: {
TreeContent,
TreePage,
},
mounted() {
this.updateProjectElements(true);
},
beforeDestroy() {
this.updateProjectElements(false);
},
methods: {
updateProjectElements(isShow) {
updateElementsVisibility('.js-show-on-project-root', isShow);
},
},
};
</script>
 
<template>
<tree-content />
<tree-page path="/" />
</template>
<script>
import TreeContent from '../components/tree_content.vue';
import { updateElementsVisibility } from '../utils/dom';
 
export default {
components: {
Loading
Loading
@@ -12,6 +13,23 @@ export default {
default: '/',
},
},
computed: {
isRoot() {
return this.path === '/';
},
},
watch: {
isRoot: {
immediate: true,
handler: 'updateElements',
},
},
methods: {
updateElements(isRoot) {
updateElementsVisibility('.js-show-on-root', isRoot);
updateElementsVisibility('.js-hide-on-root', !isRoot);
},
},
};
</script>
 
Loading
Loading
// eslint-disable-next-line import/prefer-default-export
export const updateElementsVisibility = (selector, isVisible) => {
document.querySelectorAll(selector).forEach(elem => elem.classList.toggle('hidden', !isVisible));
};
const DEFAULT_TITLE = '· GitLab';
// eslint-disable-next-line import/prefer-default-export
export const setTitle = (pathMatch, ref, project) => {
if (!pathMatch) return;
if (!pathMatch) {
document.title = `${project} ${DEFAULT_TITLE}`;
return;
}
 
const path = pathMatch.replace(/^\//, '');
const isEmpty = path === '';
 
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
document.title = `${isEmpty ? 'Files' : path} · ${ref} · ${project}`;
document.title = `${isEmpty ? 'Files' : path} · ${ref} · ${project} ${DEFAULT_TITLE}`;
};
<script>
import _ from 'underscore';
import { GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
import { GlLoadingIcon, GlSearchBoxByType, GlInfiniteScroll } from '@gitlab/ui';
import ProjectListItem from './project_list_item.vue';
 
const SEARCH_INPUT_TIMEOUT_MS = 500;
Loading
Loading
@@ -10,6 +10,7 @@ export default {
components: {
GlLoadingIcon,
GlSearchBoxByType,
GlInfiniteScroll,
ProjectListItem,
},
props: {
Loading
Loading
@@ -41,6 +42,11 @@ export default {
required: false,
default: false,
},
totalResults: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
Loading
Loading
@@ -51,6 +57,9 @@ export default {
projectClicked(project) {
this.$emit('projectClicked', project);
},
bottomReached() {
this.$emit('bottomReached');
},
isSelected(project) {
return Boolean(_.find(this.selectedProjects, { id: project.id }));
},
Loading
Loading
@@ -71,18 +80,25 @@ export default {
@input="onInput"
/>
<div class="d-flex flex-column">
<gl-loading-icon v-if="showLoadingIndicator" :size="2" class="py-2 px-4" />
<div v-if="!showLoadingIndicator" class="d-flex flex-column">
<project-list-item
v-for="project in projectSearchResults"
:key="project.id"
:selected="isSelected(project)"
:project="project"
:matcher="searchQuery"
class="js-project-list-item"
@click="projectClicked(project)"
/>
</div>
<gl-loading-icon v-if="showLoadingIndicator" :size="1" class="py-2 px-4" />
<gl-infinite-scroll
:max-list-height="402"
:fetched-items="projectSearchResults.length"
:total-items="totalResults"
@bottomReached="bottomReached"
>
<div v-if="!showLoadingIndicator" slot="items" class="d-flex flex-column">
<project-list-item
v-for="project in projectSearchResults"
:key="project.id"
:selected="isSelected(project)"
:project="project"
:matcher="searchQuery"
class="js-project-list-item"
@click="projectClicked(project)"
/>
</div>
</gl-infinite-scroll>
<div v-if="showNoResultsMessage" class="text-muted ml-2 js-no-results-message">
{{ __('Sorry, no projects matched your search') }}
</div>
Loading
Loading
Loading
Loading
@@ -297,9 +297,7 @@ module ApplicationSettingsHelper
:snowplow_iglu_registry_url,
:push_event_hooks_limit,
:push_event_activities_limit,
:custom_http_clone_url_root,
:pendo_enabled,
:pendo_url
:custom_http_clone_url_root
]
end
 
Loading
Loading
Loading
Loading
@@ -26,7 +26,8 @@ module ReleasesHelper
tag_name: @release.tag,
markdown_preview_path: preview_markdown_path(@project),
markdown_docs_path: help_page_path('user/markdown'),
releases_page_path: project_releases_path(@project, anchor: @release.tag)
releases_page_path: project_releases_path(@project, anchor: @release.tag),
update_release_api_docs_path: help_page_path('api/releases/index.md', anchor: 'update-a-release')
}
end
end
Loading
Loading
@@ -4,7 +4,7 @@ module RepositoryLanguagesHelper
def repository_languages_bar(languages)
return if languages.none?
 
content_tag :div, class: 'progress repository-languages-bar' do
content_tag :div, class: 'progress repository-languages-bar js-show-on-project-root' do
safe_join(languages.map { |lang| language_progress(lang) })
end
end
Loading
Loading
Loading
Loading
@@ -6,6 +6,12 @@ class ApplicationSetting < ApplicationRecord
include TokenAuthenticatable
include ChronicDurationAttribute
 
# Only remove this >= %12.6 and >= 2019-12-01
self.ignored_columns += %i[
pendo_enabled
pendo_url
]
add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
add_authentication_token_field :health_check_access_token
add_authentication_token_field :static_objects_external_storage_auth_token
Loading
Loading
@@ -103,11 +109,6 @@ class ApplicationSetting < ApplicationRecord
allow_blank: true,
if: :snowplow_enabled
 
validates :pendo_url,
presence: true,
public_url: true,
if: :pendo_enabled
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
Loading
Loading
Loading
Loading
@@ -135,8 +135,6 @@ module ApplicationSettingImplementation
snowplow_app_id: nil,
snowplow_iglu_registry_url: nil,
custom_http_clone_url_root: nil,
pendo_enabled: false,
pendo_url: nil,
productivity_analytics_start_date: Time.now
}
end
Loading
Loading
Loading
Loading
@@ -7,5 +7,5 @@
= render_if_exists 'admin/application_settings/slack'
= render 'admin/application_settings/third_party_offers'
= render 'admin/application_settings/snowplow'
= render_if_exists 'admin/application_settings/pendo'
= render 'admin/application_settings/eks' if Feature.enabled?(:create_eks_clusters)
Loading
Loading
@@ -90,4 +90,3 @@
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
= render 'layouts/snowplow'
= render_if_exists 'layouts/pendo'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment