Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-30 12:09:33 +00:00
parent 491c773c72
commit 55b5a8778c
106 changed files with 1472 additions and 287 deletions

View File

@ -92,6 +92,51 @@
/doc/user/search/advanced_global_search.md @marcia
/doc/user/search/advanced_search_syntax.md @marcia
/doc/user/search/index.md @marcia
/doc/administration/file_hooks.md @marcia
/doc/administration/git_annex.md @marcia
/doc/administration/git_protocol.md @marcia
/doc/administration/integration/plantuml.md @marcia
/doc/administration/invalidate_markdown_cache.md @marcia
/doc/administration/issue_closing_pattern.md @marcia
/doc/administration/lfs/index.md @marcia
/doc/administration/merge_request_diffs.md @marcia
/doc/administration/repository_checks.md @marcia
/doc/administration/snippets/index.md @marcia
/doc/administration/static_objects_external_storage.md @marcia
/doc/api/access_requests.md @marcia
/doc/api/branches.md @marcia
/doc/api/commits.md @marcia
/doc/api/discussions.md @marcia
/doc/api/group_wikis.md @marcia
/doc/api/keys.md @marcia
/doc/api/markdown.md @marcia
/doc/api/merge_request_approvals.md @marcia
/doc/api/merge_request_context_commits.md @marcia
/doc/api/merge_requests.md @marcia
/doc/api/project_aliases.md @marcia
/doc/api/project_badges.md @marcia
/doc/api/project_import_export.md @marcia
/doc/api/project_level_variables.md @marcia
/doc/api/project_snippets.md @marcia
/doc/api/project_statistics.md @marcia
/doc/api/project_templates.md @marcia
/doc/api/project_vulnerabilities.md @marcia
/doc/api/protected_branches.md @marcia
/doc/api/protected_tags.md @marcia
/doc/api/remote_mirrors.md @marcia
/doc/api/repositories.md @marcia
/doc/api/repository_files.md @marcia
/doc/api/repository_submodules.md @marcia
/doc/api/search.md @marcia
/doc/api/snippets.md @marcia
/doc/api/suggestions.md @marcia
/doc/api/tags.md @marcia
/doc/api/visual_review_discussions.md @marcia
/doc/api/wikis.md @marcia
/doc/user/admin_area/settings/account_and_limit_settings.md @marcia
/doc/user/admin_area/settings/instance_template_repository.md @marcia
/doc/user/admin_area/settings/push_event_activities_limit.md @marcia
/doc/user/admin_area/settings/visibility_and_access_controls.md @marcia
[Frontend]
*.scss @annabeldunstone @gitlab-org/maintainers/frontend

View File

@ -0,0 +1,126 @@
<script>
import { GlDrawer, GlLabel, GlAvatarLink, GlAvatarLabeled, GlLink } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
import boardsStore from '~/boards/stores/boards_store';
import eventHub from '~/sidebar/event_hub';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { inactiveId } from '~/boards/constants';
// NOTE: need to revisit how we handle headerHeight, because we have so many different header and footer options.
export default {
headerHeight: process.env.NODE_ENV === 'development' ? '75px' : '40px',
listSettingsText: __('List settings'),
assignee: 'assignee',
milestone: 'milestone',
label: 'label',
labelListText: __('Label'),
labelMilestoneText: __('Milestone'),
labelAssigneeText: __('Assignee'),
components: {
GlDrawer,
GlLabel,
GlAvatarLink,
GlAvatarLabeled,
GlLink,
BoardSettingsSidebarWipLimit: () =>
import('ee_component/boards/components/board_settings_wip_limit.vue'),
},
computed: {
...mapState(['activeId']),
activeList() {
/*
Warning: Though a computed property it is not reactive because we are
referencing a List Model class. Reactivity only applies to plain JS objects
*/
return boardsStore.state.lists.find(({ id }) => id === this.activeId);
},
isSidebarOpen() {
return this.activeId !== inactiveId;
},
activeListLabel() {
return this.activeList.label;
},
activeListMilestone() {
return this.activeList.milestone;
},
activeListAssignee() {
return this.activeList.assignee;
},
boardListType() {
return this.activeList.type || null;
},
listTypeTitle() {
switch (this.boardListType) {
case this.$options.milestone: {
return this.$options.labelMilestoneText;
}
case this.$options.label: {
return this.$options.labelListText;
}
case this.$options.assignee: {
return this.$options.labelAssigneeText;
}
default: {
return '';
}
}
},
},
created() {
eventHub.$on('sidebar.closeAll', this.closeSidebar);
},
beforeDestroy() {
eventHub.$off('sidebar.closeAll', this.closeSidebar);
},
methods: {
...mapActions(['setActiveId']),
closeSidebar() {
this.setActiveId(inactiveId);
},
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
},
};
</script>
<template>
<gl-drawer
class="js-board-settings-sidebar"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
@close="closeSidebar"
>
<template #header>{{ $options.listSettingsText }}</template>
<template v-if="isSidebarOpen">
<div class="d-flex flex-column align-items-start">
<label class="js-list-label">{{ listTypeTitle }}</label>
<template v-if="boardListType === $options.label">
<gl-label
:title="activeListLabel.title"
:background-color="activeListLabel.color"
:scoped="showScopedLabels(activeListLabel)"
/>
</template>
<template v-else-if="boardListType === $options.assignee">
<gl-avatar-link class="js-assignee" :href="activeListAssignee.webUrl">
<gl-avatar-labeled
:size="32"
:label="activeListAssignee.name"
:sub-label="`@${activeListAssignee.username}`"
:src="activeListAssignee.avatar"
/>
</gl-avatar-link>
</template>
<template v-else-if="boardListType === $options.milestone">
<gl-link class="js-milestone" :href="activeListMilestone.webUrl">
{{ activeListMilestone.title }}
</gl-link>
</template>
</div>
<board-settings-sidebar-wip-limit :max-issue-count="activeList.maxIssueCount" />
</template>
</gl-drawer>
</template>

View File

@ -83,8 +83,7 @@ export default () => {
Board: () => import('ee_else_ce/boards/components/board_column.vue'),
BoardSidebar,
BoardAddIssuesModal,
BoardSettingsSidebar: () =>
import('ee_component/boards/components/board_settings_sidebar.vue'),
BoardSettingsSidebar: () => import('~/boards/components/board_settings_sidebar.vue'),
},
store,
apolloProvider,

View File

@ -10,6 +10,10 @@ export default {
commit(types.SET_ENDPOINTS, endpoints);
},
setActiveId({ commit }, id) {
commit(types.SET_ACTIVE_ID, id);
},
fetchLists: () => {
notImplemented();
},

View File

@ -19,3 +19,4 @@ export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';

View File

@ -10,6 +10,10 @@ export default {
state.endpoints = endpoints;
},
[mutationTypes.SET_ACTIVE_ID](state, id) {
state.activeId = id;
},
[mutationTypes.REQUEST_ADD_LIST]: () => {
notImplemented();
},

View File

@ -48,7 +48,7 @@ export default {
};
},
discussionParticipants() {
return extractParticipants(this.issue.participants);
return extractParticipants(this.issue.participants.nodes);
},
resolvedDiscussions() {
return this.discussions.filter(discussion => discussion.resolved);

View File

@ -18,7 +18,7 @@ export default {
if (!this.queryVersion) return 0;
const idx = this.allVersions.findIndex(
version => this.findVersionId(version.node.id) === this.queryVersion,
version => this.findVersionId(version.id) === this.queryVersion,
);
// if the currentVersionId isn't a valid version (i.e. not in allVersions)
@ -29,7 +29,7 @@ export default {
if (this.queryVersion) return this.queryVersion;
const currentVersion = this.allVersions[this.currentVersionIdx];
return this.findVersionId(currentVersion.node.id);
return this.findVersionId(currentVersion.id);
},
dropdownText() {
if (this.isLatestVersion) {
@ -51,23 +51,21 @@ export default {
<template>
<gl-new-dropdown :text="dropdownText" size="small" class="design-version-dropdown">
<gl-new-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
<gl-new-dropdown-item v-for="(version, index) in allVersions" :key="version.id">
<router-link
class="d-flex js-version-link"
:to="{ path: $route.path, query: { version: findVersionId(version.node.id) } }"
:to="{ path: $route.path, query: { version: findVersionId(version.id) } }"
>
<div class="flex-grow-1 ml-2">
<div>
<strong
>{{ __('Version') }} {{ allVersions.length - index }}
<span v-if="findVersionId(version.node.id) === latestVersionId"
>({{ __('latest') }})</span
>
<span v-if="findVersionId(version.id) === latestVersionId">({{ __('latest') }})</span>
</strong>
</div>
</div>
<i
v-if="findVersionId(version.node.id) === currentVersionId"
v-if="findVersionId(version.id) === currentVersionId"
class="fa fa-check float-right gl-mr-2"
></i>
</router-link>

View File

@ -8,10 +8,8 @@ mutation createImageDiffNote($input: CreateImageDiffNoteInput!) {
id
replyId
notes {
edges {
node {
...DesignNote
}
nodes {
...DesignNote
}
}
}

View File

@ -5,11 +5,9 @@ mutation uploadDesign($files: [Upload!]!, $projectPath: ID!, $iid: ID!) {
designs {
...DesignItem
versions {
edges {
node {
id
sha
}
nodes {
id
sha
}
}
}

View File

@ -7,19 +7,15 @@ query getDesign($fullPath: ID!, $iid: String!, $atVersion: ID, $filenames: [Stri
issue(iid: $iid) {
designCollection {
designs(atVersion: $atVersion, filenames: $filenames) {
edges {
node {
...DesignItem
issue {
title
webPath
webUrl
participants {
edges {
node {
...Author
}
}
nodes {
...DesignItem
issue {
title
webPath
webUrl
participants {
nodes {
...Author
}
}
}

View File

@ -7,17 +7,13 @@ query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
issue(iid: $iid) {
designCollection {
designs(atVersion: $atVersion) {
edges {
node {
...DesignListItem
}
nodes {
...DesignListItem
}
}
versions {
edges {
node {
...VersionListItem
}
nodes {
...VersionListItem
}
}
}

View File

@ -2,7 +2,6 @@ import { propertyOf } from 'lodash';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import { extractNodes } from '../utils/design_management_utils';
import allVersionsMixin from './all_versions';
import { DESIGNS_ROUTE_NAME } from '../router/constants';
@ -19,9 +18,15 @@ export default {
};
},
update: data => {
const designEdges = propertyOf(data)(['project', 'issue', 'designCollection', 'designs']);
if (designEdges) {
return extractNodes(designEdges);
const designNodes = propertyOf(data)([
'project',
'issue',
'designCollection',
'designs',
'nodes',
]);
if (designNodes) {
return designNodes;
}
return [];
},

View File

@ -12,7 +12,7 @@ export default {
atVersion: null,
};
},
update: data => data.project.issue.designCollection.versions.edges,
update: data => data.project.issue.designCollection.versions.nodes,
},
},
inject: {
@ -28,7 +28,7 @@ export default {
return (
this.$route.query.version &&
this.allVersions &&
this.allVersions.some(version => version.node.id.endsWith(this.$route.query.version))
this.allVersions.some(version => version.id.endsWith(this.$route.query.version))
);
},
designsVersion() {
@ -38,7 +38,7 @@ export default {
},
latestVersionId() {
const latestVersion = this.allVersions[0];
return latestVersion && findVersionId(latestVersion.node.id);
return latestVersion && findVersionId(latestVersion.id);
},
isLatestVersion() {
if (this.allVersions.length > 0) {

View File

@ -12,10 +12,10 @@ import {
const deleteDesignsFromStore = (store, query, selectedDesigns) => {
const data = store.readQuery(query);
const changedDesigns = data.project.issue.designCollection.designs.edges.filter(
({ node }) => !selectedDesigns.includes(node.filename),
const changedDesigns = data.project.issue.designCollection.designs.nodes.filter(
node => !selectedDesigns.includes(node.filename),
);
data.project.issue.designCollection.designs.edges = [...changedDesigns];
data.project.issue.designCollection.designs.nodes = [...changedDesigns];
store.writeQuery({
...query,
@ -34,11 +34,10 @@ const addNewVersionToStore = (store, query, version) => {
if (!version) return;
const data = store.readQuery(query);
const newEdge = { node: version, __typename: 'DesignVersionEdge' };
data.project.issue.designCollection.versions.edges = [
newEdge,
...data.project.issue.designCollection.versions.edges,
data.project.issue.designCollection.versions.nodes = [
version,
...data.project.issue.designCollection.versions.nodes,
];
store.writeQuery({
@ -59,18 +58,15 @@ const addDiscussionCommentToStore = (store, createNote, query, queryVariables, d
design.notesCount += 1;
if (
!design.issue.participants.edges.some(
participant => participant.node.username === createNote.note.author.username,
!design.issue.participants.nodes.some(
participant => participant.username === createNote.note.author.username,
)
) {
design.issue.participants.edges = [
...design.issue.participants.edges,
design.issue.participants.nodes = [
...design.issue.participants.nodes,
{
__typename: 'UserEdge',
node: {
__typename: 'User',
...createNote.note.author,
},
__typename: 'User',
...createNote.note.author,
},
];
}
@ -108,18 +104,15 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) =
const notesCount = design.notesCount + 1;
design.discussions.nodes = [...design.discussions.nodes, newDiscussion];
if (
!design.issue.participants.edges.some(
participant => participant.node.username === createImageDiffNote.note.author.username,
!design.issue.participants.nodes.some(
participant => participant.username === createImageDiffNote.note.author.username,
)
) {
design.issue.participants.edges = [
...design.issue.participants.edges,
design.issue.participants.nodes = [
...design.issue.participants.nodes,
{
__typename: 'UserEdge',
node: {
__typename: 'User',
...createImageDiffNote.note.author,
},
__typename: 'User',
...createImageDiffNote.note.author,
},
];
}
@ -166,9 +159,9 @@ const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables
const addNewDesignToStore = (store, designManagementUpload, query) => {
const data = store.readQuery(query);
const newDesigns = data.project.issue.designCollection.designs.edges.reduce((acc, design) => {
if (!acc.find(d => d.filename === design.node.filename)) {
acc.push(design.node);
const newDesigns = data.project.issue.designCollection.designs.nodes.reduce((acc, design) => {
if (!acc.find(d => d.filename === design.filename)) {
acc.push(design);
}
return acc;
@ -178,30 +171,27 @@ const addNewDesignToStore = (store, designManagementUpload, query) => {
const findNewVersions = designManagementUpload.designs.find(design => design.versions);
if (findNewVersions) {
const findNewVersionsEdges = findNewVersions.versions.edges;
const findNewVersionsNodes = findNewVersions.versions.nodes;
if (findNewVersionsEdges && findNewVersionsEdges.length) {
newVersionNode = [findNewVersionsEdges[0]];
if (findNewVersionsNodes && findNewVersionsNodes.length) {
newVersionNode = [findNewVersionsNodes[0]];
}
}
const newVersions = [
...(newVersionNode || []),
...data.project.issue.designCollection.versions.edges,
...data.project.issue.designCollection.versions.nodes,
];
const updatedDesigns = {
__typename: 'DesignCollection',
designs: {
__typename: 'DesignConnection',
edges: newDesigns.map(design => ({
__typename: 'DesignEdge',
node: design,
})),
nodes: newDesigns,
},
versions: {
__typename: 'DesignVersionConnection',
edges: newVersions,
nodes: newVersions,
},
};

View File

@ -5,17 +5,7 @@ export const isValidDesignFile = ({ type }) =>
(type.match(VALID_DESIGN_FILE_MIMETYPE.regex) || []).length > 0;
/**
* Returns formatted array that doesn't contain
* `edges`->`node` nesting
*
* @param {Array} elements
*/
export const extractNodes = elements => elements.edges.map(({ node }) => node);
/**
* Returns formatted array of discussions that doesn't contain
* `edges`->`node` nesting for child notes
* Returns formatted array of discussions
*
* @param {Array} discussions
*/
@ -40,9 +30,9 @@ export const findVersionId = id => (id.match('::Version/(.+$)') || [])[1];
export const findNoteId = id => (id.match('DiffNote/(.+$)') || [])[1];
export const extractDesigns = data => data.project.issue.designCollection.designs.edges;
export const extractDesigns = data => data.project.issue.designCollection.designs.nodes;
export const extractDesign = data => (extractDesigns(data) || [])[0]?.node;
export const extractDesign = data => (extractDesigns(data) || [])[0];
/**
* Generates optimistic response for a design upload mutation
@ -72,13 +62,10 @@ export const designUploadOptimisticResponse = files => {
},
versions: {
__typename: 'DesignVersionConnection',
edges: {
__typename: 'DesignVersionEdge',
node: {
__typename: 'DesignVersion',
id: -uniqueId(),
sha: -uniqueId(),
},
nodes: {
__typename: 'DesignVersion',
id: -uniqueId(),
sha: -uniqueId(),
},
},
}));
@ -123,6 +110,6 @@ const normalizeAuthor = author => ({
avatar_url: author.avatarUrl,
});
export const extractParticipants = users => users.edges.map(({ node }) => normalizeAuthor(node));
export const extractParticipants = users => users.map(node => normalizeAuthor(node));
export const getPageLayoutElement = () => document.querySelector('.layout-page');

View File

@ -14,6 +14,7 @@ import {
} from '@gitlab/ui';
import Tracking from '~/tracking';
import PackageActivity from './activity.vue';
import PackageHistory from './package_history.vue';
import PackageInformation from './information.vue';
import PackageTitle from './package_title.vue';
import ConanInstallation from './conan_installation.vue';
@ -57,6 +58,7 @@ export default {
PackagesListLoader,
PackageListRow,
DependencyRow,
PackageHistory,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -66,6 +68,7 @@ export default {
trackingActions: { ...TrackingActions },
computed: {
...mapState([
'projectName',
'packageEntity',
'packageFiles',
'isLoading',
@ -74,6 +77,7 @@ export default {
'svgPath',
'npmPath',
'npmHelpPath',
'oneColumnView',
]),
installationComponent() {
switch (this.packageEntity.package_type) {
@ -219,29 +223,37 @@ export default {
<gl-tabs>
<gl-tab :title="__('Detail')">
<div class="row" data-qa-selector="package_information_content">
<div class="col-sm-6">
<package-information :information="packageInformation" />
<package-information
v-if="packageMetadata"
:heading="packageMetadataTitle"
:information="packageMetadata"
:show-copy="true"
/>
<template v-if="!oneColumnView">
<div
class="row"
data-qa-selector="package_information_content"
data-testid="old-package-info"
>
<div class="col-sm-6">
<package-information :information="packageInformation" />
<package-information
v-if="packageMetadata"
:heading="packageMetadataTitle"
:information="packageMetadata"
:show-copy="true"
/>
</div>
<div class="col-sm-6">
<component
:is="installationComponent"
v-if="installationComponent"
:name="packageEntity.name"
:registry-url="npmPath"
:help-url="npmHelpPath"
/>
</div>
</div>
<div class="col-sm-6">
<component
:is="installationComponent"
v-if="installationComponent"
:name="packageEntity.name"
:registry-url="npmPath"
:help-url="npmHelpPath"
/>
</div>
</div>
<package-activity />
</template>
<package-activity />
<package-history v-else :package-entity="packageEntity" :project-name="projectName" />
<h3 class="gl-font-lg">{{ __('Files') }}</h3>
<gl-table

View File

@ -0,0 +1,35 @@
<script>
import { GlIcon } from '@gitlab/ui';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
export default {
name: 'HistoryElement',
components: {
GlIcon,
TimelineEntryItem,
},
props: {
icon: {
type: String,
required: true,
},
},
};
</script>
<template>
<timeline-entry-item class="system-note note-wrapper gl-my-6!">
<div class="timeline-icon">
<gl-icon :name="icon" />
</div>
<div class="timeline-content">
<div class="note-header">
<span>
<slot></slot>
</span>
</div>
<div class="note-body"></div>
</div>
</timeline-entry-item>
</template>

View File

@ -0,0 +1,114 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import HistoryElement from './history_element.vue';
export default {
name: 'PackageHistory',
i18n: {
createdOn: s__('PackageRegistry|%{name} version %{version} was created %{datetime}'),
updatedAtText: s__('PackageRegistry|%{name} version %{version} was updated %{datetime}'),
commitText: s__('PackageRegistry|Commit %{link} on branch %{branch}'),
pipelineText: s__('PackageRegistry|Pipeline %{link} triggered %{datetime} by %{author}'),
publishText: s__('PackageRegistry|Published to the %{project} Package Registry %{datetime}'),
},
components: {
GlLink,
GlSprintf,
HistoryElement,
TimeAgoTooltip,
},
props: {
packageEntity: {
type: Object,
required: true,
},
projectName: {
type: String,
required: true,
},
},
data() {
return {
showDescription: false,
};
},
computed: {
packagePipeline() {
return this.packageEntity.pipeline?.id ? this.packageEntity.pipeline : null;
},
},
};
</script>
<template>
<div class="issuable-discussion">
<h3 class="gl-ml-6" data-testid="title">{{ __('History') }}</h3>
<ul class="timeline main-notes-list notes gl-my-4" data-testid="timeline">
<history-element icon="clock" data-testid="created-on">
<gl-sprintf :message="$options.i18n.createdOn">
<template #name>
<strong>{{ packageEntity.name }}</strong>
</template>
<template #version>
<strong>{{ packageEntity.version }}</strong>
</template>
<template #datetime>
<time-ago-tooltip :time="packageEntity.created_at" />
</template>
</gl-sprintf>
</history-element>
<history-element icon="pencil" data-testid="updated-at">
<gl-sprintf :message="$options.i18n.updatedAtText">
<template #name>
<strong>{{ packageEntity.name }}</strong>
</template>
<template #version>
<strong>{{ packageEntity.version }}</strong>
</template>
<template #datetime>
<time-ago-tooltip :time="packageEntity.updated_at" />
</template>
</gl-sprintf>
</history-element>
<template v-if="packagePipeline">
<history-element icon="commit" data-testid="commit">
<gl-sprintf :message="$options.i18n.commitText">
<template #link>
<gl-link :href="`../../commit/${packagePipeline.sha}`">{{
packagePipeline.sha
}}</gl-link>
</template>
<template #branch>
<strong>{{ packagePipeline.ref }}</strong>
</template>
</gl-sprintf>
</history-element>
<history-element icon="pipeline" data-testid="pipeline">
<gl-sprintf :message="$options.i18n.pipelineText">
<template #link>
<gl-link :href="`../../pipelines/${packagePipeline.id}`"
>#{{ packagePipeline.id }}</gl-link
>
</template>
<template #datetime>
<time-ago-tooltip :time="packagePipeline.created_at" />
</template>
<template #author>{{ packagePipeline.user.name }}</template>
</gl-sprintf>
</history-element>
</template>
<history-element icon="package" data-testid="published">
<gl-sprintf :message="$options.i18n.publishText">
<template #project>
<strong>{{ projectName }}</strong>
</template>
<template #datetime>
<time-ago-tooltip :time="packageEntity.created_at" />
</template>
</gl-sprintf>
</history-element>
</ul>
</div>
</template>

View File

@ -1,4 +1,5 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import PackagesApp from './components/app.vue';
import Translate from '~/vue_shared/translate';
import createStore from './store';
@ -7,7 +8,7 @@ Vue.use(Translate);
export default () => {
const el = document.querySelector('#js-vue-packages-detail');
const { package: packageJson, canDelete: canDeleteStr, ...rest } = el.dataset;
const { package: packageJson, canDelete: canDeleteStr, oneColumnView, ...rest } = el.dataset;
const packageEntity = JSON.parse(packageJson);
const canDelete = canDeleteStr === 'true';
@ -15,6 +16,7 @@ export default () => {
packageEntity,
packageFiles: packageEntity.package_files,
canDelete,
oneColumnView: parseBoolean(oneColumnView),
...rest,
});

View File

@ -51,7 +51,7 @@ export default {
<div class="js-file-title file-title-flex-parent">
<div class="file-header-content">
<i aria-hidden="true" class="fa fa-file-text-o fa-fw"></i>
<gl-link :href="blob.webUrl">
<gl-link :href="blob.webPath">
<strong>{{ blob.name }}</strong>
</gl-link>
</div>

View File

@ -8,14 +8,14 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
titleHtml
descriptionHtml
message
webUrl
webPath
authoredDate
authorName
authorGravatar
author {
name
avatarUrl
webUrl
webPath
}
signatureHtml
pipelines(ref: $ref, first: 1) {

View File

@ -114,4 +114,15 @@ class SearchController < ApplicationController
Gitlab::UsageDataCounters::SearchCounter.count(:navbar_searches)
end
def append_info_to_payload(payload)
super
# Merging to :metadata will ensure these are logged as top level keys
payload[:metadata] || {}
payload[:metadata]['meta.search.group_id'] = params[:group_id]
payload[:metadata]['meta.search.project_id'] = params[:project_id]
payload[:metadata]['meta.search.search'] = params[:search]
payload[:metadata]['meta.search.scope'] = params[:scope]
end
end

View File

@ -524,8 +524,6 @@ module Ci
end
end
CI_REGISTRY_USER = 'gitlab-ci-token'
def persisted_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless persisted?
@ -537,7 +535,7 @@ module Ci
.append(key: 'CI_JOB_TOKEN', value: token.to_s, public: false, masked: true)
.append(key: 'CI_BUILD_ID', value: id.to_s)
.append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false, masked: true)
.append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
.append(key: 'CI_REGISTRY_USER', value: ::Gitlab::Auth::CI_REGISTRY_USER)
.append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false, masked: true)
.append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false)
.concat(deploy_token_variables)
@ -596,7 +594,7 @@ module Ci
def repo_url
return unless token
auth = "gitlab-ci-token:#{token}@"
auth = "#{::Gitlab::Auth::CI_JOB_USER}:#{token}@"
project.http_url_to_repo.sub(%r{^https?://}) do |prefix|
prefix + auth
end

View File

@ -19,4 +19,6 @@
nuget_help_path: help_page_path('user/packages/nuget_repository/index'),
pypi_path: pypi_registry_url(@project.id),
pypi_setup_path: package_registry_project_url(@project.id, :pypi),
pypi_help_path: help_page_path('user/packages/pypi_repository/index') } }
pypi_help_path: help_page_path('user/packages/pypi_repository/index'),
project_name: @project.name,
one_column_view: Feature.enabled?(:packages_details_one_column, @project).to_s } }

View File

@ -0,0 +1,5 @@
---
title: Improve Elasticsearch Reindexing documentation
merge_request: 29788
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Remove per-web-transaction redis metrics
merge_request: 38101
author:
type: other

View File

@ -147,7 +147,6 @@ if Gitlab::Metrics.enabled? && !Rails.env.test? && !(Rails.env.development? && d
Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::Metrics::RackMiddleware)
config.middleware.use(Gitlab::Middleware::RailsQueueDuration)
config.middleware.use(Gitlab::Metrics::RedisRackMiddleware)
config.middleware.use(Gitlab::Metrics::ElasticsearchRackMiddleware)
end

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---
# File hooks
> - Introduced in GitLab 10.6.

View File

@ -1,4 +1,8 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, howto
disqus_identifier: 'https://docs.gitlab.com/ee/workflow/git_annex.html'
---

View File

@ -1,4 +1,8 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
description: "Set and configure Git protocol v2"
---

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, howto
---
# PlantUML & GitLab
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/8537) in GitLab 8.16.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---
# Invalidate Markdown Cache
For performance reasons, GitLab caches the HTML version of Markdown text

View File

@ -1,4 +1,8 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, howto
disqus_identifier: 'https://docs.gitlab.com/ee/workflow/lfs/lfs_administration.html'
---

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Editor
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---
# Merge request diffs storage **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/52568) in GitLab 11.8.

View File

@ -97,8 +97,6 @@ The following metrics are available:
| `gitlab_transaction_db_count_total` | Counter | 13.1 | Counter for total number of SQL calls | `controller`, `action` |
| `gitlab_transaction_db_write_count_total` | Counter | 13.1 | Counter for total number of write SQL calls | `controller`, `action` |
| `gitlab_transaction_db_cached_count_total` | Counter | 13.1 | Counter for total number of cached SQL calls | `controller`, `action` |
| `http_redis_requests_duration_seconds` | Histogram | 13.1 | Redis requests duration during web transactions | `controller`, `action` |
| `http_redis_requests_total` | Counter | 13.1 | Redis requests count during web transactions | `controller`, `action` |
| `http_elasticsearch_requests_duration_seconds` **(STARTER)** | Histogram | 13.1 | Elasticsearch requests duration during web transactions | `controller`, `action` |
| `http_elasticsearch_requests_total` **(STARTER)** | Counter | 13.1 | Elasticsearch requests count during web transactions | `controller`, `action` |
| `pipelines_created_total` | Counter | 9.4 | Counter of pipelines created | |

View File

@ -97,7 +97,7 @@ This is why you will need:
When using default setup, minimum configuration requires:
- `CONSUL_USERNAME`. Defaults to `gitlab-consul`
- `CONSUL_USERNAME`. The default user for Omnibus GitLab is `gitlab-consul`
- `CONSUL_DATABASE_PASSWORD`. Password for the database user.
- `CONSUL_PASSWORD_HASH`. This is a hash generated out of Consul username/password pair.
Can be generated with:
@ -140,7 +140,7 @@ server nodes.
We will need the following password information for the application's database user:
- `POSTGRESQL_USERNAME`. Defaults to `gitlab`
- `POSTGRESQL_USERNAME`. The default user for Omnibus GitLab is `gitlab`
- `POSTGRESQL_USER_PASSWORD`. The password for the database user
- `POSTGRESQL_PASSWORD_HASH`. This is a hash generated out of the username/password pair.
Can be generated with:
@ -153,7 +153,7 @@ We will need the following password information for the application's database u
When using default setup, minimum configuration requires:
- `PGBOUNCER_USERNAME`. Defaults to `pgbouncer`
- `PGBOUNCER_USERNAME`. The default user for Omnibus GitLab is `pgbouncer`
- `PGBOUNCER_PASSWORD`. This is a password for PgBouncer service.
- `PGBOUNCER_PASSWORD_HASH`. This is a hash generated out of PgBouncer username/password pair.
Can be generated with:

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Editor
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---
# Repository checks
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/3232) in GitLab 8.7.

View File

@ -2,7 +2,7 @@
type: reference, howto
stage: Create
group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
---
# Snippets settings **(CORE ONLY)**

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Editor
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---
# Static objects external storage
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31025) in GitLab 12.3.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Group and project access requests API
> Introduced in GitLab 8.11.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Branches API
This API operates on [repository branches](../user/project/repository/branches/index.md).

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Commits API
## List repository commits

View File

@ -1,7 +1,8 @@
---
stage: Plan
group: Project Management
stage: Create
group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: reference, api
---
# Discussions API

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Knowledge
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Wikis API
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212199) in GitLab 13.2.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Keys API
## Get SSH key with user by ID of an SSH key

View File

@ -1,7 +1,8 @@
---
stage: Plan
group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Markdown API

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Merge request approvals API **(STARTER)**
Configuration for approvals on all Merge Requests (MR) in the project. Must be authenticated for all endpoints.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Merge request context commits API
## List MR context commits

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Merge requests API
Every API call to merge requests must be authenticated.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project Aliases API **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3264) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.1.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project badges API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17082) in GitLab 10.6.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project import/export API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41899) in GitLab 10.6.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project-level Variables API
## List project variables

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Editor
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project snippets
## Snippet visibility level

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project statistics API
Every API call to [project](../user/project/index.md) statistics must be authenticated.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project templates API
This API is a project-specific version of these endpoints:

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project Vulnerabilities API **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.6.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Protected branches API
> Introduced in GitLab 9.5.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Protected tags API
> Introduced in GitLab 11.3.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Project remote mirrors API
[Push mirrors](../user/project/repository/repository_mirroring.md#pushing-to-a-remote-repository-core)

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Repositories API
## List repository tree

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Repository files API
**CRUD for repository files**

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Repository submodules API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41213) in GitLab 11.5

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Search API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41763) in GitLab 10.5.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Editor
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Snippets API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/6373) in GitLab 8.15.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Suggest Changes API
Every API call to suggestions must be authenticated.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Tags API
## List project repository tags

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Visual Review discussions API **(STARTER)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18710) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.5.

View File

@ -1,3 +1,10 @@
---
stage: Create
group: Knowledge
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference, api
---
# Wikis API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13372) in GitLab 10.0.

View File

@ -121,6 +121,9 @@ Patterns:
## Zero downtime reindexing with multiple indices
NOTE: **Note:**
This is not applicable yet as multiple indices functionality is not fully implemented.
Currently GitLab can only handle a single version of setting. Any setting/schema changes would require reindexing everything from scratch. Since reindexing can take a long time, this can cause search functionality downtime.
To avoid downtime, GitLab is working to support multiple indices that

View File

@ -423,6 +423,140 @@ or creating [extra Sidekiq processes](../administration/operations/extra_sidekiq
For repository and snippet files, GitLab will only index up to 1 MiB of content, in order to avoid indexing timeouts.
## Zero downtime reindexing
The idea behind this reindexing method is to leverage Elasticsearch index alias feature to atomically swap between two indices.
We will refer to each index as `primary` (online and used by GitLab for read/writes) and `secondary` (offline, for reindexing purpose).
Instead of connecting directly to the `primary` index, we'll setup an index alias such as we can change the underlying index at will.
NOTE: **Note:**
Any index attached to the production alias is deemed a `primary` and will end up being used by the GitLab Elasticsearch integration.
### Pause the indexing
Under **Admin Area > Integration > Elasticsearch**, check the **Pause Elasticsearch Indexing** setting and save.
With this, all updates that should happen on your Elasticsearch index will be buffered and caught up once unpaused.
### Setup
TIP: **Tip:**
If your index has been created with GitLab v13.0+ you can skip directly to [trigger the reindex](#trigger-the-reindex-via-the-elasticsearch-administration).
This process involves multiple shell commands and curl invocations, so a good initial setup will help down the road:
```shell
# You can find this value under Admin Area > Integration > Elasticsearch > URL
export CLUSTER_URL="http://localhost:9200"
export PRIMARY_INDEX="gitlab-production"
export SECONDARY_INDEX="gitlab-production-$(date +%s)"
```
### Reclaiming the `gitlab-production` index name
CAUTION: **Caution:**
It is highly recommended that you take a snapshot of your cluster to make sure there is a recovery path if anything goes wrong.
NOTE: **Note:**
Due to a technical limitation, there will be a slight downtime because of the fact that we need to reclaim the current `primary` index to be used as the alias.
To reclaim the `gitlab-production` index name, you need to first create a `secondary` index and then trigger the re-index from `primary`.
#### Creating a secondary index
To create a secondary index, run the following Rake task. The `SKIP_ALIAS`
environment variable will disable the automatic creation of the Elasticsearch
alias, which would conflict with the existing index under `$PRIMARY_INDEX`:
```shell
# Omnibus installation
sudo SKIP_ALIAS=1 gitlab-rake "gitlab:elastic:create_empty_index[$SECONDARY_INDEX]"
# Source installation
SKIP_ALIAS=1 bundle exec rake "gitlab:elastic:create_empty_index[$SECONDARY_INDEX]"
```
The index should be created successfully, with the latest index options and mappings.
#### Trigger the re-index from `primary`
To trigger the re-index from `primary` index:
1. Use the Elasticsearch [Reindex API](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/docs-reindex.html):
```shell
curl --request POST \
--header 'Content-Type: application/json' \
--data "{ \"source\": { \"index\": \"$PRIMARY_INDEX\" }, \"dest\": { \"index\": \"$SECONDARY_INDEX\" } }" \
"$CLUSTER_URL/_reindex?slices=auto&wait_for_completion=false"
```
There will be an output like:
```plaintext
{"task":"3qw_Tr0YQLq7PF16Xek8YA:1012"}
```
Note the `task` value here as it will be useful to follow the reindex progress.
1. Wait for the reindex process to complete, by checking the `completed` value.
Using the `task` value form the previous step:
```shell
export TASK_ID=3qw_Tr0YQLq7PF16Xek8YA:1012
curl "$CLUSTER_URL/_tasks/$TASK_ID?pretty"
```
The output will be like:
```plaintext
{"completed":false, …}
```
Once the returned value is `true`, you may continue to the next step.
1. Make sure that the secondary index has data in it. You can use the Elasticsearch
API to look for the index size and compare our two indices:
```shell
curl $CLUSTER_URL/$PRIMARY_INDEX/_count => 123123
curl $CLUSTER_URL/$SECONDARY_INDEX/_count => 123123
```
TIP: **Tip:**
Comparing the document count is more accurate than using the index size, as improvements to the storage might cause the new index to be smaller than the original one.
1. Once you are confident your `secondary` index is valid, you can process to the creation of the alias.
```shell
# Delete the original index
curl --request DELETE $CLUSTER_URL/$PRIMARY_INDEX
# Create the alias and add the `secondary` index to it
curl --request POST \
--header 'Content-Type: application/json' \
--data "{\"actions\":[{\"add\":{\"index\":\"$SECONDARY_INDEX\",\"alias\":\"$PRIMARY_INDEX\"}}]}}" \
$CLUSTER_URL/_aliases
```
The reindexing is now completed. Your GitLab instance is now ready to use the [automated in-cluster reindexing](#trigger-the-reindex-via-the-elasticsearch-administration) feature for future reindexing.
1. Unpause the indexing
Under **Admin Area > Integration > Elasticsearch**, uncheck the **Pause Elasticsearch Indexing** setting and save.
### Trigger the reindex via the Elasticsearch administration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34069) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.2.
Under **Admin Area > Integration > Elasticsearch zero-downtime reindexing**, click on **Trigger cluster reindexing**.
NOTE: **Note:**
Reindexing can be a lengthy process depending on the size of your Elasticsearch cluster.
While the reindexing is running, you will be able to follow its progress under that same section.
## GitLab Elasticsearch Rake tasks
Rake tasks are available to:
@ -586,7 +720,7 @@ Here are some common pitfalls and how to overcome them:
- **I indexed all the repositories but then switched Elasticsearch servers and now I can't find anything**
You will need to re-run all the Rake tasks to re-index the database, repositories, and wikis.
You will need to re-run all the Rake tasks to reindex the database, repositories, and wikis.
- **The indexing process is taking a very long time**

View File

@ -1,4 +1,7 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---

View File

@ -1,4 +1,8 @@
---
stage: Create
group: Gitaly
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
type: reference
---

View File

@ -1,4 +1,7 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---

View File

@ -1,4 +1,7 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---

View File

@ -1,4 +1,7 @@
---
stage: Create
group: Source Code
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers"
type: reference
---

View File

@ -31,7 +31,7 @@ file path fragments to start seeing results.
## Syntax highlighting
> Support for `.gitlab.ci.yml` validation [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218472) in GitLab 13.2.
> Support for `.gitlab-ci.yml` validation [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218472) in GitLab 13.2.
As expected from an IDE, syntax highlighting for many languages within
the Web IDE will make your direct editing even easier.

View File

@ -26,6 +26,9 @@ module Gitlab
# Default scopes for OAuth applications that don't define their own
DEFAULT_SCOPES = [:api].freeze
CI_JOB_USER = 'gitlab-ci-token'
CI_REGISTRY_USER = 'gitlab-ci-token'
class << self
prepend_if_ee('EE::Gitlab::Auth') # rubocop: disable Cop/InjectEnterpriseEditionModule
@ -126,7 +129,7 @@ module Gitlab
# rubocop:enable Gitlab/RailsLogger
def skip_rate_limit?(login:)
::Ci::Build::CI_REGISTRY_USER == login
CI_REGISTRY_USER == login
end
def look_to_limit_user(actor)
@ -254,7 +257,7 @@ module Gitlab
end
def build_access_token_check(login, password)
return unless login == 'gitlab-ci-token'
return unless login == CI_JOB_USER
return unless password
build = find_build_by_token(password)

View File

@ -82,7 +82,7 @@ module Gitlab
login, password = user_name_and_password(current_request)
return unless login.present? && password.present?
return unless ::Ci::Build::CI_REGISTRY_USER == login
return unless ::Gitlab::Auth::CI_REGISTRY_USER == login
job = ::Ci::Build.find_by_token(password)
raise UnauthorizedError unless job

View File

@ -1,36 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Metrics
# Rack middleware for tracking Redis metrics from Grape and Web requests.
class RedisRackMiddleware
def initialize(app)
@app = app
end
def call(env)
transaction = Gitlab::Metrics.current_transaction
@app.call(env)
ensure
record_metrics(transaction)
end
private
def record_metrics(transaction)
query_time = Gitlab::Instrumentation::Redis.query_time
request_count = Gitlab::Instrumentation::Redis.get_request_count
transaction.increment(:http_redis_requests_total, request_count) do
docstring 'Amount of calls to Redis servers during web requests'
end
transaction.observe(:http_redis_requests_duration_seconds, query_time) do
docstring 'Query time for Redis servers during web requests'
buckets Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS
end
end
end
end
end

View File

@ -16774,12 +16774,21 @@ msgstr ""
msgid "Package was removed"
msgstr ""
msgid "PackageRegistry|%{name} version %{version} was created %{datetime}"
msgstr ""
msgid "PackageRegistry|%{name} version %{version} was updated %{datetime}"
msgstr ""
msgid "PackageRegistry|Add Conan Remote"
msgstr ""
msgid "PackageRegistry|Add NuGet Source"
msgstr ""
msgid "PackageRegistry|Commit %{link} on branch %{branch}"
msgstr ""
msgid "PackageRegistry|Composer"
msgstr ""
@ -16897,6 +16906,12 @@ msgstr ""
msgid "PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
msgstr ""
msgid "PackageRegistry|Pipeline %{link} triggered %{datetime} by %{author}"
msgstr ""
msgid "PackageRegistry|Published to the %{project} Package Registry %{datetime}"
msgstr ""
msgid "PackageRegistry|Published to the repository at %{timestamp}"
msgstr ""

View File

@ -216,4 +216,23 @@ RSpec.describe SearchController do
it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
end
describe '#append_info_to_payload' do
it 'appends search metadata for logging' do
last_payload = nil
original_append_info_to_payload = controller.method(:append_info_to_payload)
expect(controller).to receive(:append_info_to_payload) do |payload|
original_append_info_to_payload.call(payload)
last_payload = payload
end
get :show, params: { scope: 'issues', search: 'hello world', group_id: '123', project_id: '456' }
expect(last_payload[:metadata]['meta.search.group_id']).to eq('123')
expect(last_payload[:metadata]['meta.search.project_id']).to eq('456')
expect(last_payload[:metadata]['meta.search.search']).to eq('hello world')
expect(last_payload[:metadata]['meta.search.scope']).to eq('issues')
end
end
end

View File

@ -0,0 +1,237 @@
import '~/boards/models/list';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDrawer, GlLabel, GlAvatarLink, GlAvatarLabeled } from '@gitlab/ui';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
import boardsStore from '~/boards/stores/boards_store';
import sidebarEventHub from '~/sidebar/event_hub';
import { inactiveId } from '~/boards/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('BoardSettingsSidebar', () => {
let wrapper;
let mock;
let storeActions;
const labelTitle = 'test';
const labelColor = '#FFFF';
const listId = 1;
const createComponent = (state = { activeId: inactiveId }, actions = {}) => {
storeActions = actions;
const store = new Vuex.Store({
state,
actions: storeActions,
});
wrapper = shallowMount(BoardSettingsSidebar, {
store,
localVue,
});
};
const findLabel = () => wrapper.find(GlLabel);
const findDrawer = () => wrapper.find(GlDrawer);
beforeEach(() => {
boardsStore.create();
});
afterEach(() => {
jest.restoreAllMocks();
wrapper.destroy();
});
it('finds a GlDrawer component', () => {
createComponent();
expect(findDrawer().exists()).toBe(true);
});
describe('on close', () => {
it('calls closeSidebar', async () => {
const spy = jest.fn();
createComponent({ activeId: inactiveId }, { setActiveId: spy });
findDrawer().vm.$emit('close');
await wrapper.vm.$nextTick();
expect(storeActions.setActiveId).toHaveBeenCalledWith(
expect.anything(),
inactiveId,
undefined,
);
});
it('calls closeSidebar on sidebar.closeAll event', async () => {
createComponent({ activeId: inactiveId }, { setActiveId: jest.fn() });
sidebarEventHub.$emit('sidebar.closeAll');
await wrapper.vm.$nextTick();
expect(storeActions.setActiveId).toHaveBeenCalledWith(
expect.anything(),
inactiveId,
undefined,
);
});
});
describe('when activeId is zero', () => {
it('renders GlDrawer with open false', () => {
createComponent();
expect(findDrawer().props('open')).toBe(false);
});
});
describe('when activeId is greater than zero', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
list_type: 'label',
});
});
afterEach(() => {
boardsStore.removeList(listId);
});
it('renders GlDrawer with open false', () => {
createComponent({ activeId: 1 });
expect(findDrawer().props('open')).toBe(true);
});
});
describe('when activeId is in boardsStore', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({
id: listId,
label: { title: labelTitle, color: labelColor },
list_type: 'label',
});
createComponent({ activeId: listId });
});
afterEach(() => {
mock.restore();
});
it('renders label title', () => {
expect(findLabel().props('title')).toBe(labelTitle);
});
it('renders label background color', () => {
expect(findLabel().props('backgroundColor')).toBe(labelColor);
});
});
describe('when activeId is not in boardsStore', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
boardsStore.addList({ id: listId, label: { title: labelTitle, color: labelColor } });
createComponent({ activeId: inactiveId });
});
afterEach(() => {
mock.restore();
});
it('does not render GlLabel', () => {
expect(findLabel().exists()).toBe(false);
});
});
describe('when activeList is present', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
boardsStore.removeList(listId);
});
describe('when list type is "milestone"', () => {
beforeEach(() => {
boardsStore.addList({
id: 1,
milestone: {
webUrl: 'https://gitlab.com/h5bp/html5-boilerplate/-/milestones/1',
title: 'Backlog',
},
max_issue_count: 1,
list_type: 'milestone',
});
});
afterEach(() => {
boardsStore.removeList(1, 'milestone');
wrapper.destroy();
});
it('renders the correct milestone text', () => {
createComponent({ activeId: 1 });
expect(wrapper.find('.js-milestone').text()).toBe('Backlog');
});
it('renders the correct list type text', () => {
createComponent({ activeId: 1 });
expect(wrapper.find('.js-list-label').text()).toBe('Milestone');
});
});
describe('when list type is "assignee"', () => {
beforeEach(() => {
boardsStore.addList({
id: 1,
user: { username: 'root', avatar: '', name: 'Test', webUrl: 'https://gitlab.com/root' },
max_issue_count: 1,
list_type: 'assignee',
});
});
afterEach(() => {
boardsStore.removeList(1, 'assignee');
wrapper.destroy();
});
it('renders gl-avatar-link with correct href', () => {
createComponent({ activeId: 1 });
expect(wrapper.find(GlAvatarLink).exists()).toBe(true);
expect(wrapper.find(GlAvatarLink).attributes('href')).toBe('https://gitlab.com/root');
});
it('renders gl-avatar-labeled with "root" as username and name as "Test"', () => {
createComponent({ activeId: 1 });
expect(wrapper.find(GlAvatarLabeled).exists()).toBe(true);
expect(wrapper.find(GlAvatarLabeled).attributes('label')).toBe('Test');
expect(wrapper.find(GlAvatarLabeled).attributes('sublabel')).toBe('@root');
});
it('renders the correct list type text', () => {
createComponent({ activeId: 1 });
expect(wrapper.find('.js-list-label').text()).toBe('Assignee');
});
});
});
});

View File

@ -1,6 +1,7 @@
import actions from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
import testAction from 'helpers/vuex_action_helper';
import { inactiveId } from '~/boards/constants';
const expectNotImplemented = action => {
it('is not implemented', () => {
@ -25,6 +26,23 @@ describe('setEndpoints', () => {
});
});
describe('setActiveId', () => {
it('should commit mutation SET_ACTIVE_ID', done => {
const state = {
activeId: inactiveId,
};
testAction(
actions.setActiveId,
1,
state,
[{ type: types.SET_ACTIVE_ID, payload: 1 }],
[],
done,
);
});
});
describe('fetchLists', () => {
expectNotImplemented(actions.fetchLists);
});

View File

@ -32,6 +32,16 @@ describe('Board Store Mutations', () => {
});
});
describe('SET_ACTIVE_ID', () => {
it('updates aciveListId to be the value that is passed', () => {
const expectedId = 1;
mutations.SET_ACTIVE_ID(state, expectedId);
expect(state.activeId).toBe(expectedId);
});
});
describe('REQUEST_ADD_LIST', () => {
expectNotImplemented(mutations.REQUEST_ADD_LIST);
});

View File

@ -1,14 +1,10 @@
export default [
{
node: {
id: 'gid://gitlab/DesignManagement::Version/3',
sha: '0945756378e0b1588b9dd40d5a6b99e8b7198f55',
},
id: 'gid://gitlab/DesignManagement::Version/3',
sha: '0945756378e0b1588b9dd40d5a6b99e8b7198f55',
},
{
node: {
id: 'gid://gitlab/DesignManagement::Version/2',
sha: '5b063fef0cd7213b312db65b30e24f057df21b20',
},
id: 'gid://gitlab/DesignManagement::Version/2',
sha: '5b063fef0cd7213b312db65b30e24f057df21b20',
},
];

View File

@ -1,8 +1,6 @@
export default [
{
node: {
id: 'gid://gitlab/DesignManagement::Version/1',
sha: 'b389071a06c153509e11da1f582005b316667001',
},
id: 'gid://gitlab/DesignManagement::Version/1',
sha: 'b389071a06c153509e11da1f582005b316667001',
},
];

View File

@ -12,14 +12,12 @@ export default {
webPath: 'full-issue-path',
webUrl: 'full-issue-url',
participants: {
edges: [
nodes: [
{
node: {
name: 'Administrator',
username: 'root',
webUrl: 'link-to-author',
avatarUrl: 'link-to-avatar',
},
name: 'Administrator',
username: 'root',
webUrl: 'link-to-author',
avatarUrl: 'link-to-avatar',
},
],
},

View File

@ -5,11 +5,7 @@ export default {
issue: {
designCollection: {
designs: {
edges: [
{
node: design,
},
],
nodes: [design],
},
},
},

View File

@ -3,7 +3,7 @@ export default {
issue: {
designCollection: {
designs: {
edges: [],
nodes: [],
},
},
},

View File

@ -57,9 +57,7 @@ const mockDesigns = [
];
const mockVersion = {
node: {
id: 'gid://gitlab/DesignManagement::Version/1',
},
id: 'gid://gitlab/DesignManagement::Version/1',
};
describe('Design management index page', () => {
@ -240,13 +238,10 @@ describe('Design management index page', () => {
},
versions: {
__typename: 'DesignVersionConnection',
edges: {
__typename: 'DesignVersionEdge',
node: {
__typename: 'DesignVersion',
id: expect.anything(),
sha: expect.anything(),
},
nodes: {
__typename: 'DesignVersion',
id: expect.anything(),
sha: expect.anything(),
},
},
},

View File

@ -51,7 +51,7 @@ describe('extractDiscussions', () => {
};
});
it('discards the edges.node artifacts of GraphQL', () => {
it('discards the node artifacts of GraphQL', () => {
expect(extractDiscussions(discussions)).toEqual([
{ id: 1, notes: ['a'], index: 1 },
{ id: 2, notes: ['b'], index: 2 },
@ -96,10 +96,7 @@ describe('optimistic responses', () => {
discussions: { __typename: 'DesignDiscussion', nodes: [] },
versions: {
__typename: 'DesignVersionConnection',
edges: {
__typename: 'DesignVersionEdge',
node: { __typename: 'DesignVersion', id: -1, sha: -1 },
},
nodes: { __typename: 'DesignVersion', id: -1, sha: -1 },
},
},
],

View File

@ -62,14 +62,14 @@ exports[`PackageActivity render to match the default snapshot when there is a pi
<!---->
<gl-link-stub
href="../../commit/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
href="../../commit/sha-baz"
>
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sha-baz
</gl-link-stub>
<clipboard-button-stub
cssclass="border-0 text-secondary py-0"
text="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
text="sha-baz"
title="Copy commit SHA"
tooltipplacement="top"
/>

View File

@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`History Element renders the correct markup 1`] = `
<li
class="timeline-entry system-note note-wrapper gl-my-6!"
>
<div
class="timeline-entry-inner"
>
<div
class="timeline-icon"
>
<gl-icon-stub
name="pencil"
size="16"
/>
</div>
<div
class="timeline-content"
>
<div
class="note-header"
>
<span>
<div
data-testid="default-slot"
/>
</span>
</div>
<div
class="note-body"
/>
</div>
</div>
</li>
`;

View File

@ -16,6 +16,8 @@ import ConanInstallation from '~/packages/details/components/conan_installation.
import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
import DependencyRow from '~/packages/details/components/dependency_row.vue';
import PackageHistory from '~/packages/details/components/package_history.vue';
import PackageActivity from '~/packages/details/components/activity.vue';
import {
conanPackage,
mavenPackage,
@ -39,6 +41,7 @@ describe('PackagesApp', () => {
packageEntity = mavenPackage,
packageFiles = mavenFiles,
isLoading = false,
oneColumnView = false,
} = {}) {
store = new Vuex.Store({
state: {
@ -50,6 +53,8 @@ describe('PackagesApp', () => {
emptySvgPath: 'empty-illustration',
npmPath: 'foo',
npmHelpPath: 'foo',
projectName: 'bar',
oneColumnView,
},
actions: {
fetchPackageVersions,
@ -93,6 +98,9 @@ describe('PackagesApp', () => {
const dependenciesCountBadge = () => wrapper.find('[data-testid="dependencies-badge"]');
const noDependenciesMessage = () => wrapper.find('[data-testid="no-dependencies-message"]');
const dependencyRows = () => wrapper.findAll(DependencyRow);
const findPackageHistory = () => wrapper.find(PackageHistory);
const findPackageActivity = () => wrapper.find(PackageActivity);
const findOldPackageInfo = () => wrapper.find('[data-testid="old-package-info"]');
afterEach(() => {
wrapper.destroy();
@ -286,4 +294,31 @@ describe('PackagesApp', () => {
);
});
});
describe('one column layout feature flag', () => {
describe.each`
oneColumnView | history | oldInfo | activity
${true} | ${true} | ${false} | ${false}
${false} | ${false} | ${true} | ${true}
`(
'with oneColumnView set to $oneColumnView',
({ oneColumnView, history, oldInfo, activity }) => {
beforeEach(() => {
createComponent({ oneColumnView });
});
it('package history', () => {
expect(findPackageHistory().exists()).toBe(history);
});
it('old info block', () => {
expect(findOldPackageInfo().exists()).toBe(oldInfo);
});
it('package activity', () => {
expect(findPackageActivity().exists()).toBe(activity);
});
},
);
});
});

View File

@ -0,0 +1,57 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import component from '~/packages/details/components/history_element.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
describe('History Element', () => {
let wrapper;
const defaultProps = {
icon: 'pencil',
};
const mountComponent = () => {
wrapper = shallowMount(component, {
propsData: { ...defaultProps },
stubs: {
TimelineEntryItem,
},
slots: {
default: '<div data-testid="default-slot"></div>',
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findTimelineEntry = () => wrapper.find(TimelineEntryItem);
const findGlIcon = () => wrapper.find(GlIcon);
const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
it('renders the correct markup', () => {
mountComponent();
expect(wrapper.element).toMatchSnapshot();
});
it('has a default slot', () => {
mountComponent();
expect(findDefaultSlot().exists()).toBe(true);
});
it('has a timeline entry', () => {
mountComponent();
expect(findTimelineEntry().exists()).toBe(true);
});
it('has an icon', () => {
mountComponent();
const icon = findGlIcon();
expect(icon.exists()).toBe(true);
expect(icon.attributes('name')).toBe(defaultProps.icon);
});
});

View File

@ -0,0 +1,106 @@
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import component from '~/packages/details/components/package_history.vue';
import { mavenPackage, mockPipelineInfo } from '../../mock_data';
describe('Package History', () => {
let wrapper;
const defaultProps = {
projectName: 'baz project',
packageEntity: { ...mavenPackage },
};
const mountComponent = props => {
wrapper = shallowMount(component, {
propsData: { ...defaultProps, ...props },
stubs: {
HistoryElement: '<div data-testid="history-element"><slot></slot></div>',
GlSprintf,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findHistoryElement = testId => wrapper.find(`[data-testid="${testId}"]`);
const findElementLink = container => container.find(GlLink);
const findElementTimeAgo = container => container.find(TimeAgoTooltip);
const findTitle = () => wrapper.find('[data-testid="title"]');
const findTimeline = () => wrapper.find('[data-testid="timeline"]');
it('has the correct title', () => {
mountComponent();
const title = findTitle();
expect(title.exists()).toBe(true);
expect(title.text()).toBe('History');
});
it('has a timeline container', () => {
mountComponent();
const title = findTimeline();
expect(title.exists()).toBe(true);
expect(title.classes()).toEqual(
expect.arrayContaining(['timeline', 'main-notes-list', 'notes']),
);
});
describe.each`
name | icon | text | timeAgoTooltip | link
${'created-on'} | ${'clock'} | ${'Test package version 1.0.0 was created'} | ${mavenPackage.created_at} | ${null}
${'updated-at'} | ${'pencil'} | ${'Test package version 1.0.0 was updated'} | ${mavenPackage.updated_at} | ${null}
${'commit'} | ${'commit'} | ${'Commit sha-baz on branch branch-name'} | ${null} | ${'../../commit/sha-baz'}
${'pipeline'} | ${'pipeline'} | ${'Pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${'../../pipelines/1'}
${'published'} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null}
`('history element $name', ({ name, icon, text, timeAgoTooltip, link }) => {
let element;
beforeEach(() => {
mountComponent({ packageEntity: { ...mavenPackage, pipeline: mockPipelineInfo } });
element = findHistoryElement(name);
});
it('has the correct icon', () => {
expect(element.props('icon')).toBe(icon);
});
it('has the correct text', () => {
expect(element.text()).toBe(text);
});
it('time-ago tooltip', () => {
const timeAgo = findElementTimeAgo(element);
const exist = Boolean(timeAgoTooltip);
expect(timeAgo.exists()).toBe(exist);
if (exist) {
expect(timeAgo.props('time')).toBe(timeAgoTooltip);
}
});
it('link', () => {
const linkElement = findElementLink(element);
const exist = Boolean(link);
expect(linkElement.exists()).toBe(exist);
if (exist) {
expect(linkElement.attributes('href')).toBe(link);
}
});
});
describe('when pipelineInfo is missing', () => {
it.each(['commit', 'pipeline'])('%s history element is hidden', name => {
mountComponent();
expect(findHistoryElement(name).exists()).toBe(false);
});
});
});

View File

@ -6,7 +6,7 @@ const _links = {
export const mockPipelineInfo = {
id: 1,
ref: 'branch-name',
sha: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
sha: 'sha-baz',
user: {
name: 'foo',
},
@ -14,6 +14,7 @@ export const mockPipelineInfo = {
name: 'foo-project',
web_url: 'foo-project-link',
},
created_at: '2015-12-10',
};
export const mavenPackage = {

View File

@ -24,14 +24,14 @@ exports[`publish_method renders 1`] = `
<gl-link-stub
class="mr-1"
href="../commit/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
href="../commit/sha-baz"
>
xxxxxxxx
sha-baz
</gl-link-stub>
<clipboard-button-stub
cssclass="border-0 text-secondary py-0 px-1"
text="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
text="sha-baz"
title="Copy commit SHA"
tooltipplacement="top"
/>

Some files were not shown because too many files have changed in this diff Show More