Commit c3f24fcb authored by Mike Greiling's avatar Mike Greiling

Merge branch 'master' into 'ps-ide-defer-loading-terminal-modules'

# Conflicts:
#   doc/development/testing_guide/frontend_testing.md
parents 14d8c620 5891d8c0
......@@ -20,8 +20,7 @@ export const init = ({ dispatch }, { endpoint, logState, pagePath }) => {
logState,
pagePath,
});
return Promise.all([dispatch('fetchJob'), dispatch('fetchTrace')]);
dispatch('fetchJob');
};
export const setJobEndpoint = ({ commit }, endpoint) => commit(types.SET_JOB_ENDPOINT, endpoint);
......@@ -39,6 +38,7 @@ export const toggleSidebar = ({ dispatch, state }) => {
};
let eTagPoll;
let isTraceReadyForRender;
export const clearEtagPoll = () => {
eTagPoll = null;
......@@ -70,7 +70,14 @@ export const fetchJob = ({ state, dispatch }) => {
});
if (!Visibility.hidden()) {
eTagPoll.makeRequest();
// eslint-disable-next-line promise/catch-or-return
eTagPoll.makeRequest().then(() => {
// if a job is canceled we still need to dispatch
// fetchTrace to get the trace so we check for has_trace
if (state.job.started || state.job.has_trace) {
dispatch('fetchTrace');
}
});
} else {
axios
.get(state.jobEndpoint)
......@@ -80,9 +87,15 @@ export const fetchJob = ({ state, dispatch }) => {
Visibility.change(() => {
if (!Visibility.hidden()) {
// This check is needed to ensure the loading icon
// is not shown for a finished job during a visibility change
if (!isTraceReadyForRender) {
dispatch('startPollingTrace');
}
dispatch('restartPolling');
} else {
dispatch('stopPolling');
dispatch('stopPollingTrace');
}
});
};
......@@ -163,6 +176,8 @@ export const fetchTrace = ({ dispatch, state }) =>
params: { state: state.traceState },
})
.then(({ data }) => {
isTraceReadyForRender = data.complete;
dispatch('toggleScrollisInBottom', isScrolledToBottom());
dispatch('receiveTraceSuccess', data);
......
......@@ -49,6 +49,7 @@ export default {
[types.SET_TRACE_TIMEOUT](state, id) {
state.traceTimeout = id;
state.isTraceComplete = false;
},
/**
......
<script>
import { GlPagination } from '@gitlab/ui';
import { GlKeysetPagination } from '@gitlab/ui';
import ImageListRow from './image_list_row.vue';
export default {
name: 'ImageList',
components: {
GlPagination,
GlKeysetPagination,
ImageListRow,
},
props: {
......@@ -13,19 +13,14 @@ export default {
type: Array,
required: true,
},
pagination: {
pageInfo: {
type: Object,
required: true,
},
},
computed: {
currentPage: {
get() {
return this.pagination.page;
},
set(page) {
this.$emit('pageChange', page);
},
showPagination() {
return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage;
},
},
};
......@@ -40,13 +35,15 @@ export default {
:first="index === 0"
@delete="$emit('delete', $event)"
/>
<gl-pagination
v-model="currentPage"
:per-page="pagination.perPage"
:total-items="pagination.total"
align="center"
class="w-100 gl-mt-3"
<div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination
v-if="showPagination"
:has-next-page="pageInfo.hasNextPage"
:has-previous-page="pageInfo.hasPreviousPage"
class="gl-mt-3"
@prev="$emit('prev-page')"
@next="$emit('next-page')"
/>
</div>
</div>
</template>
<script>
import { GlTooltipDirective, GlIcon, GlSprintf } from '@gitlab/ui';
import { n__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import DeleteButton from '../delete_button.vue';
......@@ -11,6 +13,8 @@ import {
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS,
} from '../../constants/index';
export default {
......@@ -38,19 +42,29 @@ export default {
},
computed: {
disabledDelete() {
return !this.item.destroy_path || this.item.deleting;
return !this.item.canDelete || this.deleting;
},
id() {
return getIdFromGraphQLId(this.item.id);
},
deleting() {
return this.item.status === IMAGE_DELETE_SCHEDULED_STATUS;
},
failedDelete() {
return this.item.status === IMAGE_FAILED_DELETED_STATUS;
},
tagsCountText() {
return n__(
'ContainerRegistry|%{count} Tag',
'ContainerRegistry|%{count} Tags',
this.item.tags_count,
this.item.tagsCount,
);
},
warningIconText() {
if (this.item.failedDelete) {
if (this.failedDelete) {
return ASYNC_DELETE_IMAGE_ERROR_MESSAGE;
} else if (this.item.cleanup_policy_started_at) {
}
if (this.item.expirationPolicyStartedAt) {
return CLEANUP_TIMED_OUT_ERROR_MESSAGE;
}
return null;
......@@ -63,23 +77,23 @@ export default {
<list-item
v-gl-tooltip="{
placement: 'left',
disabled: !item.deleting,
disabled: !deleting,
title: $options.i18n.ROW_SCHEDULED_FOR_DELETION,
}"
v-bind="$attrs"
:disabled="item.deleting"
:disabled="deleting"
>
<template #left-primary>
<router-link
class="gl-text-body gl-font-weight-bold"
data-testid="details-link"
:to="{ name: 'details', params: { id: item.id } }"
:to="{ name: 'details', params: { id } }"
>
{{ item.path }}
</router-link>
<clipboard-button
v-if="item.location"
:disabled="item.deleting"
:disabled="deleting"
:text="item.location"
:title="item.location"
category="tertiary"
......@@ -97,7 +111,7 @@ export default {
<gl-icon name="tag" class="gl-mr-2" />
<gl-sprintf :message="tagsCountText">
<template #count>
{{ item.tags_count }}
{{ item.tagsCount }}
</template>
</gl-sprintf>
</span>
......@@ -106,7 +120,7 @@ export default {
<delete-button
:title="$options.i18n.REMOVE_REPOSITORY_LABEL"
:disabled="disabledDelete"
:tooltip-disabled="Boolean(item.destroy_path)"
:tooltip-disabled="item.canDelete"
:tooltip-title="$options.i18n.LIST_DELETE_BUTTON_DISABLED"
@delete="$emit('delete', item)"
/>
......
......@@ -44,5 +44,6 @@ export const EMPTY_RESULT_MESSAGE = s__(
// Parameters
export const IMAGE_DELETE_SCHEDULED_STATUS = 'delete_scheduled';
export const IMAGE_FAILED_DELETED_STATUS = 'delete_failed';
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
export const IMAGE_FAILED_DELETED_STATUS = 'DELETE_FAILED';
export const GRAPHQL_PAGE_SIZE = 10;
fragment ContainerRepositoryFields on ContainerRepository {
id
name
path
status
location
canDelete
createdAt
tagsCount
expirationPolicyStartedAt
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
export const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
assumeImmutableResults: true,
},
),
});
mutation destroyContainerRepository($id: ContainerRepositoryID!) {
destroyContainerRepository(input: { id: $id }) {
containerRepository {
id
status
}
errors
}
}
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/container_repository.fragment.graphql"
query getProjectContainerRepositories(
$fullPath: ID!
$name: String
$first: Int
$last: Int
$after: String
$before: String
) {
group(fullPath: $fullPath) {
containerRepositoriesCount
containerRepositories(name: $name, after: $after, before: $before, first: $first, last: $last) {
nodes {
...ContainerRepositoryFields
}
pageInfo {
...PageInfo
}
}
}
}
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/container_repository.fragment.graphql"
query getProjectContainerRepositories(
$fullPath: ID!
$name: String
$first: Int
$last: Int
$after: String
$before: String
) {
project(fullPath: $fullPath) {
containerRepositoriesCount
containerRepositories(name: $name, after: $after, before: $before, first: $first, last: $last) {
nodes {
...ContainerRepositoryFields
}
pageInfo {
...PageInfo
}
}
}
}
......@@ -5,6 +5,7 @@ import RegistryExplorer from './pages/index.vue';
import RegistryBreadcrumb from './components/registry_breadcrumb.vue';
import { createStore } from './stores';
import createRouter from './router';
import { apolloProvider } from './graphql/index';
Vue.use(Translate);
Vue.use(GlToast);
......@@ -27,6 +28,7 @@ export default () => {
el,
store,
router,
apolloProvider,
components: {
RegistryExplorer,
},
......
<script>
import { mapState, mapActions } from 'vuex';
import { mapState } from 'vuex';
import {
GlEmptyState,
GlTooltipDirective,
......@@ -11,6 +11,7 @@ import {
GlSearchBoxByClick,
} from '@gitlab/ui';
import Tracking from '~/tracking';
import createFlash from '~/flash';
import ProjectEmptyState from '../components/list_page/project_empty_state.vue';
import GroupEmptyState from '../components/list_page/group_empty_state.vue';
......@@ -18,6 +19,10 @@ import RegistryHeader from '../components/list_page/registry_header.vue';
import ImageList from '../components/list_page/image_list.vue';
import CliCommands from '../components/list_page/cli_commands.vue';
import getProjectContainerRepositories from '../graphql/queries/get_project_container_repositories.graphql';
import getGroupContainerRepositories from '../graphql/queries/get_group_container_repositories.graphql';
import deleteContainerRepository from '../graphql/mutations/delete_container_repository.graphql';
import {
DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
......@@ -29,6 +34,8 @@ import {
IMAGE_REPOSITORY_LIST_LABEL,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
GRAPHQL_PAGE_SIZE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
} from '../constants/index';
export default {
......@@ -66,21 +73,63 @@ export default {
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
},
apollo: {
images: {
query() {
return this.graphQlQuery;
},
variables() {
return this.queryVariables;
},
update(data) {
return data[this.graphqlResource]?.containerRepositories.nodes;
},
result({ data }) {
this.pageInfo = data[this.graphqlResource]?.containerRepositories?.pageInfo;
this.containerRepositoriesCount = data[this.graphqlResource]?.containerRepositoriesCount;
},
error() {
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
},
},
},
data() {
return {
images: [],
pageInfo: {},
containerRepositoriesCount: 0,
itemToDelete: {},
deleteAlertType: null,
search: null,
isEmpty: false,
searchValue: null,
name: null,
mutationLoading: false,
};
},
computed: {
...mapState(['config', 'isLoading', 'images', 'pagination']),
...mapState(['config']),
graphqlResource() {
return this.config.isGroupPage ? 'group' : 'project';
},
graphQlQuery() {
return this.config.isGroupPage
? getGroupContainerRepositories
: getProjectContainerRepositories;
},
queryVariables() {
return {
name: this.name,
fullPath: this.config.isGroupPage ? this.config.groupPath : this.config.projectPath,
first: GRAPHQL_PAGE_SIZE,
};
},
tracking() {
return {
label: 'registry_repository_delete',
};
},
isLoading() {
return this.$apollo.queries.images.loading || this.mutationLoading;
},
showCommands() {
return Boolean(!this.isLoading && !this.config?.isGroupPage && this.images?.length);
},
......@@ -93,19 +142,7 @@ export default {
: DELETE_IMAGE_ERROR_MESSAGE;
},
},
mounted() {
this.loadImageList(this.$route.name);
},
methods: {
...mapActions(['requestImagesList', 'requestDeleteImage']),
loadImageList(fromName) {
if (!fromName || !this.images?.length) {
return this.requestImagesList().then(() => {
this.isEmpty = this.images.length === 0;
});
}
return Promise.resolve();
},
deleteImage(item) {
this.track('click_button');
this.itemToDelete = item;
......@@ -113,18 +150,59 @@ export default {
},
handleDeleteImage() {
this.track('confirm_delete');
return this.requestDeleteImage(this.itemToDelete)
.then(() => {
this.mutationLoading = true;
return this.$apollo
.mutate({
mutation: deleteContainerRepository,
variables: {
id: this.itemToDelete.id,
},
})
.then(({ data }) => {
if (data?.destroyContainerRepository?.errors[0]) {
this.deleteAlertType = 'danger';
} else {
this.deleteAlertType = 'success';
}
})
.catch(() => {
this.deleteAlertType = 'danger';
})
.finally(() => {
this.mutationLoading = false;
});
},
dismissDeleteAlert() {
this.deleteAlertType = null;
this.itemToDelete = {};
},
fetchNextPage() {
if (this.pageInfo?.hasNextPage) {
this.$apollo.queries.images.fetchMore({
variables: {
after: this.pageInfo?.endCursor,
first: GRAPHQL_PAGE_SIZE,
},
updateQuery(previousResult, { fetchMoreResult }) {
return fetchMoreResult;
},
});
}
},
fetchPreviousPage() {
if (this.pageInfo?.hasPreviousPage) {
this.$apollo.queries.images.fetchMore({
variables: {
first: null,
before: this.pageInfo?.startCursor,
last: GRAPHQL_PAGE_SIZE,
},
updateQuery(previousResult, { fetchMoreResult }) {
return fetchMoreResult;
},
});
}
},
},
};
</script>
......@@ -134,7 +212,7 @@ export default {
<gl-alert
v-if="showDeleteAlert"
:variant="deleteAlertType"
class="mt-2"
class="gl-mt-5"
dismissible
@dismiss="dismissDeleteAlert"
>
......@@ -165,7 +243,7 @@ export default {
<template v-else>
<registry-header
:images-count="pagination.total"
:images-count="containerRepositoriesCount"
:expiration-policy="config.expirationPolicy"
:help-page-path="config.helpPagePath"
:expiration-policy-help-page-path="config.expirationPolicyHelpPagePath"
......@@ -176,7 +254,7 @@ export default {
</template>
</registry-header>
<div v-if="isLoading" class="mt-2">
<div v-if="isLoading" class="gl-mt-5">
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
:key="index"
......@@ -190,16 +268,17 @@ export default {
</gl-skeleton-loader>
</div>
<template v-else>
<template v-if="!isEmpty">
<template v-if="images.length > 0 || name">
<div class="gl-display-flex gl-p-1 gl-mt-3" data-testid="listHeader">
<div class="gl-flex-fill-1">
<h5>{{ $options.i18n.IMAGE_REPOSITORY_LIST_LABEL }}</h5>
</div>
<div>
<gl-search-box-by-click
v-model="search"
v-model="searchValue"
:placeholder="$options.i18n.SEARCH_PLACEHOLDER_TEXT"
@submit="requestImagesList({ name: $event })"
@clear="name = null"
@submit="name = $event"
/>
</div>
</div>
......@@ -207,9 +286,10 @@ export default {
<image-list
v-if="images.length"
:images="images"
:pagination="pagination"
@pageChange="requestImagesList({ pagination: { page: $event }, name: search })"
:page-info="pageInfo"
@delete="deleteImage"
@prev-page="fetchPreviousPage"
@next-page="fetchNextPage"
/>
<gl-empty-state
......
......@@ -11,7 +11,7 @@ module Mutations
argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the alert to mutate"
description: "The IID of the alert to mutate"
field :alert,
Types::AlertManagement::AlertType,
......
......@@ -8,7 +8,7 @@ module Mutations
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
required: true,
description: "The id of the integration to remove"
description: "The ID of the integration to remove"
def resolve(id:)
integration = authorized_find!(id: id)
......
......@@ -8,7 +8,7 @@ module Mutations
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
required: true,
description: "The id of the integration to mutate"
description: "The ID of the integration to mutate"
def resolve(id:)
integration = authorized_find!(id: id)
......
......@@ -8,7 +8,7 @@ module Mutations
argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
required: true,
description: "The id of the integration to mutate"
description: "The ID of the integration to mutate"
argument :name, GraphQL::STRING_TYPE,
required: false,
......
......@@ -8,7 +8,7 @@ module Mutations
argument :id, Types::GlobalIDType[::PrometheusService],
required: true,
description: "The id of the integration to mutate"
description: "The ID of the integration to mutate"
def resolve(id:)
integration = authorized_find!(id: id)
......
......@@ -8,7 +8,7 @@ module Mutations
argument :id, Types::GlobalIDType[::PrometheusService],
required: true,
description: "The id of the integration to mutate"
description: "The ID of the integration to mutate"
argument :active, GraphQL::BOOLEAN_TYPE,
required: false,
......
......@@ -12,7 +12,7 @@ module Mutations
argument :awardable_id,
::Types::GlobalIDType[::Awardable],
required: true,
description: 'The global id of the awardable resource'
description: 'The global ID of the awardable resource'
argument :name,
GraphQL::STRING_TYPE,
......
......@@ -7,7 +7,7 @@ module Mutations
argument :id, PipelineID,
required: true,
description: 'The id of the pipeline to mutate'
description: 'The ID of the pipeline to mutate'
private
......
......@@ -11,7 +11,7 @@ module Mutations
argument :iid, GraphQL::ID_TYPE,
required: true,
description: "The iid of the issue to modify designs for"
description: "The IID of the issue to modify designs for"
private
......
......@@ -10,7 +10,7 @@ module Mutations
argument :id,
Types::GlobalIDType[Discussion],
required: true,
description: 'The global id of the discussion'
description: 'The global ID of the discussion'
argument :resolve,
GraphQL::BOOLEAN_TYPE,
......
......@@ -11,7 +11,7 @@ module Mutations
argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the merge request to mutate"
description: "The IID of the merge request to mutate"
field :merge_request,
Types::MergeRequestType,
......
......@@ -20,12 +20,12 @@ module Mutations
argument :environment_id,
::Types::GlobalIDType[::Environment],
required: false,
description: 'The global id of the environment to add an annotation to'
description: 'The global ID of the environment to add an annotation to'
argument :cluster_id,
::Types::GlobalIDType[::Clusters::Cluster],
required: false,
description: 'The global id of the cluster to add an annotation to'
description: 'The global ID of the cluster to add an annotation to'
argument :starting_at, Types::TimeType,
required: true,
......
......@@ -11,7 +11,7 @@ module Mutations
argument :noteable_id,
::Types::GlobalIDType[::Noteable],
required: true,
description: 'The global id of the resource to add a note to'
description: 'The global ID of the resource to add a note to'
argument :body,
GraphQL::STRING_TYPE,
......
......@@ -9,7 +9,7 @@ module Mutations
argument :discussion_id,
::Types::GlobalIDType[::Discussion],
required: false,
description: 'The global id of the discussion this note is in reply to'
description: 'The global ID of the discussion this note is in reply to'
private
......
......@@ -10,7 +10,7 @@ module Mutations
argument :id,
::Types::GlobalIDType[::Note],
required: true,
description: 'The global id of the note to destroy'
description: 'The global ID of the note to destroy'
def resolve(id:)
note = authorized_find!(id: id)
......
......@@ -16,7 +16,7 @@ module Mutations
loads: Types::Notes::NoteType,
as: :note,
required: true,
description: 'The global id of the DiffNote to update'
description: 'The global ID of the DiffNote to update'
argument :position,
Types::Notes::UpdateDiffImagePositionInputType,
......
......@@ -11,7 +11,7 @@ module Mutations
argument :id,
::Types::GlobalIDType[::Note],
required: true,
description: 'The global id of the note to update'
description: 'The global ID of the note to update'
def resolve(args)
note = authorized_find!(id: args[:id])
......
......@@ -9,7 +9,7 @@ module Mutations
argument :id, ::Types::GlobalIDType[::Snippet],
required: true,
description: 'The global id of the snippet to destroy'
description: 'The global ID of the snippet to destroy'
def resolve(id:)
snippet = authorized_find!(id: id)
......
......@@ -7,7 +7,7 @@ module Mutations
argument :id, ::Types::GlobalIDType[::Snippet],
required: true,
description: 'The global id of the snippet to update'
description: 'The global ID of the snippet to update'
def resolve(id:)
snippet = authorized_find!(id: id)
......
......@@ -9,7 +9,7 @@ module Mutations
argument :id, ::Types::GlobalIDType[::Snippet],
required: true,
description: 'The global id of the snippet to update'
description: 'The global ID of the snippet to update'
argument :title, GraphQL::STRING_TYPE,
required: false,
......
......@@ -10,7 +10,7 @@ module Mutations
argument :id,
::Types::GlobalIDType[::Todo],
required: true,
description: 'The global id of the todo to mark as done'
description: 'The global ID of the todo to mark as done'
field :todo, Types::TodoType,
null: false,
......
......@@ -10,7 +10,7 @@ module Mutations
argument :id,
::Types::GlobalIDType[::Todo],
required: true,
description: 'The global id of the todo to restore'
description: 'The global ID of the todo to restore'
field :todo, Types::TodoType,
null: false,
......
......@@ -10,11 +10,11 @@ module Mutations
argument :ids,
[::Types::GlobalIDType[::Todo]],
required: true,
description: 'The global ids of the todos to restore (a maximum of 50 is supported at once)'
description: 'The global IDs of the todos to restore (a maximum of 50 is supported at once)'
field :updated_ids, [::Types::GlobalIDType[Todo]],
null: false,
description: 'The ids of the updated todo items',
description: 'The IDs of the updated todo items',
deprecated: { reason: 'Use todos', milestone: '13.2' }
field :todos, [::Types::TodoType],
......
......@@ -8,7 +8,7 @@ module Types
argument :jira_account_id,
GraphQL::STRING_TYPE,
required: true,
description: 'Jira account id of the user'
description: 'Jira account ID of the user'
argument :gitlab_id,
GraphQL::INT_TYPE,
required: false,
......
......@@ -16,4 +16,5 @@
"cleanup_policies_help_page_path" => help_page_path('user/packages/container_registry/index', anchor: 'how-the-cleanup-policy-works'),
"is_admin": current_user&.admin.to_s,
is_group_page: "true",
"group_path": @group.full_path,
character_error: @character_error.to_s } }
......@@ -17,6 +17,6 @@
"garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'container-registry-garbage-collection'),
"run_cleanup_policies_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'run-the-cleanup-policy-now'),
"cleanup_policies_help_page_path" => help_page_path('user/packages/container_registry/index', anchor: 'how-the-cleanup-policy-works'),
"project_path": @project.full_path,
"is_admin": current_user&.admin.to_s,
character_error: @character_error.to_s } }
---
title: Refactor container registry list page to grapqhl
merge_request: 48602
author:
type: changed
---
title: Update template to use codequality 0.85.18-gitlab.1
merge_request: 49034
author:
type: changed
---
title: Ensure job trace endpoint is not called if the current job has not started or the browser is not visible
merge_request: 48516
author:
type: fixed
......@@ -484,6 +484,7 @@ sudo
swimlane
swimlanes
syslog
tanuki
tcpdump
Thanos
Tiller
......
......@@ -133,7 +133,7 @@ you will need to follow the Consul [outage recovery](#outage-recovery) process.
To be safe, it's recommended that you only restart Consul in one node at a time to
ensure the cluster remains intact. For larger clusters, it is possible to restart
multiple nodes at a time. See the
[Consul consensus document](https://www.consul.io/docs/internals/consensus.html#deployment-table)
[Consul consensus document](https://www.consul.io/docs/architecture/consensus#deployment-table)
for how many failures it can tolerate. This will be the number of simultaneous
restarts it can sustain.
......
......@@ -167,7 +167,7 @@ When the list of hosts is updated, it might take a while for the old connections
to be terminated. The `disconnect_timeout` setting can be used to enforce an
upper limit on the time it will take to terminate all old database connections.
Some nameservers (like [Consul](https://www.consul.io/docs/agent/dns.html#udp-based-dns-queries)) can return a truncated list of hosts when
Some nameservers (like [Consul](https://www.consul.io/docs/discovery/dns#udp-based-dns-queries)) can return a truncated list of hosts when
queried over UDP. To overcome this issue, you can use TCP for querying by setting
`use_tcp` to `true`.
......
......@@ -31,7 +31,7 @@ The following outline re-uses the [maturity metric](https://about.gitlab.com/dir
The initial step for integrating a new component with GitLab starts with creating a [Feature proposal in the issue tracker](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Feature%20proposal).
Identify the [product category](https://about.gitlab.com/handbook/product/categories/) the component falls under and assign the Engineering Manager and Product Manager responsible for that category.
Identify the [product category](https://about.gitlab.com/handbook/product/product-categories/) the component falls under and assign the Engineering Manager and Product Manager responsible for that category.
The general steps for getting any GitLab feature from proposal to release can be found in the [Product development flow](https://about.gitlab.com/handbook/product-development-flow/).
......
......@@ -33,7 +33,7 @@ The agent can help you perform tasks like these:
## Architecture of the Kubernetes Agent
The GitLab Kubernetes Agent and the GitLab Kubernetes Agent Server use
[bidirectional streaming](https://grpc.io/docs/guides/concepts/#bidirectional-streaming-rpc)
[bidirectional streaming](https://grpc.io/docs/what-is-grpc/core-concepts/#bidirectional-streaming-rpc)
to allow the connection acceptor (the gRPC server, GitLab Kubernetes Agent Server) to
act as a client. The connection acceptor sends requests as gRPC replies. The client-server
relationship is inverted because the connection must be initiated from inside the
......
......@@ -47,7 +47,7 @@ the `author` field. GitLab team members **should not**.
- Any user-facing change **must** have a changelog entry. This includes both visual changes (regardless of how minor), and changes to the rendered DOM which impact how a screen reader may announce the content.
- Any client-facing change to our REST and GraphQL APIs **must** have a changelog entry.
- Performance improvements **should** have a changelog entry.
- Changes that need to be documented in the Product Analytics [Event Dictionary](https://about.gitlab.com/handbook/product/product-analytics-guide#event-dictionary)
- Changes that need to be documented in the Product Analytics [Event Dictionary](https://about.gitlab.com/handbook/product/product-analytics-guide/#event-dictionary)
also require a changelog entry.
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
......@@ -55,7 +55,7 @@ the `author` field. GitLab team members **should not**.
- Any docs-only changes **should not** have a changelog entry.
- Any change behind a disabled feature flag **should not** have a changelog entry.
- Any change behind an enabled feature flag **should** have a changelog entry.
- Any change that adds new usage data metrics and changes that needs to be documented in Product Analytics [Event Dictionary](https://about.gitlab.com/handbook/product/product-analytics-guide#event-dictionary) **should** have a changelog entry.
- Any change that adds new usage data metrics and changes that needs to be documented in Product Analytics [Event Dictionary](https://about.gitlab.com/handbook/product/product-analytics-guide/#event-dictionary) **should** have a changelog entry.
- A change that adds snowplow events **should** have a changelog entry -
- A change that [removes a feature flag](feature_flags/development.md) **should** have a changelog entry -
only if the feature flag did not default to true already.
......
......@@ -107,7 +107,7 @@ Rendered example:
## cURL Examples
The following sections include a set of [cURL](https://curl.haxx.se) examples
The following sections include a set of [cURL](https://curl.se/) examples
you can use in the API documentation.
CAUTION: **Caution:**
......
......@@ -533,7 +533,9 @@ tenses, words, and phrases:
content is accessible to more readers.
- Don't write in the first person singular.
(Tested in [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml).)
<!-- vale gitlab.FirstPerson = NO -->
- Instead of _I_ or _me_, use _we_, _you_, _us_, or _one_.
<!-- vale gitlab.FirstPerson = YES -->
- When possible, stay user focused by writing in the second person (_you_ or
the imperative).
- Don't overuse "that". In many cases, you can remove "that" from a sentence
......@@ -795,6 +797,8 @@ Items nested in lists should always align with the first character of the list
item. In unordered lists (using `-`), this means two spaces for each level of
indentation:
<!-- vale off -->
````markdown
- Unordered list item 1
......@@ -816,8 +820,12 @@ indentation:
![an image that will nest inside list item 4](image.png)
````
<!-- vale on -->
For ordered lists, use three spaces for each level of indentation:
<!-- vale off -->
````markdown
1. Ordered list item 1
......@@ -839,6 +847,8 @@ For ordered lists, use three spaces for each level of indentation:
![an image that will nest inside list item 4](image.png)
````
<!-- vale on -->
You can nest full lists inside other lists using the same rules as above. If you
want to mix types, that's also possible, if you don't mix items at the same
level:
......@@ -904,7 +914,7 @@ Valid for Markdown content only, not for front matter entries:
- Standard quotes: double quotes (`"`). Example: "This is wrapped in double
quotes".
- Quote inside a quote: double quotes (`"`) wrap single quotes (`'`). Example:
"I am 'quoting' something in a quote".
"This sentence 'quotes' something in a quote".
For other punctuation rules, refer to the
[GitLab UX guide](https://design.gitlab.com/content/punctuation/).
......@@ -1367,6 +1377,8 @@ hidden on the documentation site, but is displayed by `/help`.
- For regular fenced code blocks, always use a highlighting class corresponding to
the language for better readability. Examples:
<!-- vale off -->
````markdown
```ruby
Ruby code
......@@ -1385,6 +1397,8 @@ hidden on the documentation site, but is displayed by `/help`.
```
````
<!-- vale on -->
Syntax highlighting is required for fenced code blocks added to the GitLab
documentation. Refer to the following table for the most common language classes,
or check the [complete list](https://github.com/rouge-ruby/rouge/wiki/List-of-supported-languages-and-lexers)
......@@ -1771,8 +1785,7 @@ for use in GitLab X.X, and is planned for [removal](link-to-issue) in GitLab X.X
```
After the feature or product is officially deprecated and removed, remove
its information from the GitLab documentation based on
the GitLab version where it's actually removed.
its information from the GitLab documentation.
### Versions in the past or future
......@@ -1926,6 +1939,8 @@ Configuration settings include:
When you document a list of steps, it may entail editing the configuration file
and reconfiguring or restarting GitLab. In that case, use these styles:
<!-- vale off -->
````markdown
**For Omnibus installations**
......@@ -1953,6 +1968,8 @@ and reconfiguring or restarting GitLab. In that case, use these styles:
GitLab for the changes to take effect.
````
<!-- vale on -->
In this case:
- Before each step list the installation method is declared in bold.
......
......@@ -15,7 +15,7 @@ the [Elasticsearch integration documentation](../integration/elasticsearch.md#en
In June 2019, Mario de la Ossa hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [Elasticsearch integration](../integration/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction.
In August 2020, a second Deep Dive was hosted, focusing on [GitLab's specific architecture for multi-indices support](#zero-downtime-reindexing-with-multiple-indices). The [recording on YouTube](https://www.youtube.com/watch?v=0WdPR9oB2fg) and the [slides](https://lulalala.gitlab.io/gitlab-elasticsearch-deepdive) are available. Everything covered in this deep dive was accurate as of GitLab 13.3.
In August 2020, a second Deep Dive was hosted, focusing on [GitLab's specific architecture for multi-indices support](#zero-downtime-reindexing-with-multiple-indices). The [recording on YouTube](https://www.youtube.com/watch?v=0WdPR9oB2fg) and the [slides](https://lulalala.gitlab.io/gitlab-elasticsearch-deepdive/) are available. Everything covered in this deep dive was accurate as of GitLab 13.3.
## Supported Versions
......
......@@ -279,6 +279,17 @@ Subsequent calls to this method for the same experiment and the same user have n
Note that this data is completely separate from the [events tracking data](#implement-the-tracking-events). They are not linked together in any way.
#### Add context
You can add arbitrary context data in a hash which gets stored as part of the experiment user record.
This data can then be used by data analytics dashboards.
```ruby
before_action do
record_experiment_user(:signup_flow, foo: 42)
end
```
### Record experiment conversion event
Along with the tracking of backend and frontend events and the [recording of experiment participants](#record-experiment-user), we can also record when a user performs the desired conversion event action. For example:
......
......@@ -411,7 +411,7 @@ handleClick() {
### Working with pagination
GitLab's GraphQL API uses [Relay-style cursor pagination](https://www.apollographql.com/docs/react/data/pagination/#cursor-based)
GitLab's GraphQL API uses [Relay-style cursor pagination](https://www.apollographql.com/docs/react/pagination/overview/#cursor-based)
for connection types. This means a "cursor" is used to keep track of where in the data
set the next items should be fetched from. [GraphQL Ruby Connection Concepts](https://graphql-ruby.org/pagination/connection_concepts.html)
is a good overview and introduction to connections.
......
......@@ -50,7 +50,7 @@ change feature flags or you do not [have access](#access).
### Enabling a feature for preproduction testing
As a first step in a feature rollout, you should enable the feature on <https://staging.gitlab.com>
As a first step in a feature rollout, you should enable the feature on <https://about.staging.gitlab.com>
and <https://dev.gitlab.org>.
These two environments have different scopes.
......
......@@ -100,7 +100,7 @@ To propose additions to the glossary please
### Inclusive language in French
<!-- vale gitlab.Spelling = NO -->
In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000036068906/)).
So, to include both genders, write “Utilisateurs et utilisatrices” instead of “Utilisateur·rice·s”.
When space is missing, the male gender should be used alone.
<!-- vale gitlab.Spelling = YES -->
......@@ -42,7 +42,7 @@ In forms we should use the `for` attribute in the label statement:
## Testing
1. On MacOS you can use [VoiceOver](https://www.apple.com/accessibility/mac/vision/) by pressing `cmd+F5`.
1. On MacOS you can use [VoiceOver](http://www.apple.com/accessibility/vision/) by pressing `cmd+F5`.
1. On Windows you can use [Narrator](https://www.microsoft.com/en-us/accessibility/windows) by pressing Windows logo key + Control + Enter.
## Online resources
......
......@@ -488,9 +488,9 @@ We have the following recommendations for [Adding new events](#adding-new-events
Events are tracked behind [feature flags](../feature_flags/index.md) due to concerns for Redis performance and scalability.
For a full list of events and coresponding feature flags see, [known_events](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters/known_events/) files.
For a full list of events and corresponding feature flags see, [known_events](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters/known_events/) files.
To enable or disable tracking for specific event within <https://gitlab.com> or <https://staging.gitlab.com>, run commands such as the following to
To enable or disable tracking for specific event within <https://gitlab.com> or <https://about.staging.gitlab.com>, run commands such as the following to
[enable or disable the corresponding feature](../feature_flags/index.md).
```shell
......@@ -666,7 +666,7 @@ We also use `#database-lab` and [explain.depesz.com](https://explain.depesz.com/
### 5. Add the metric definition
When adding, changing, or updating metrics, please update the [Event Dictionary's **Usage Ping** table](https://about.gitlab.com/handbook/product/product-analytics-guide#event-dictionary).
When adding, changing, or updating metrics, please update the [Event Dictionary's **Usage Ping** table](https://about.gitlab.com/handbook/product/product-analytics-guide/#event-dictionary).
### 6. Add new metric to Versions Application
......
......@@ -192,8 +192,8 @@ Following you'll find some general common practices you will find as part of our
When it comes to querying DOM elements in your tests, it is best to uniquely and semantically target
the element.
Preferentially, this is done by targeting what the user actually sees using [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro).
When selecting by text it is best to use [`getByRole` or `findByRole`](https://testing-library.com/docs/dom-testing-library/api-queries#byrole)
Preferentially, this is done by targeting what the user actually sees using [DOM Testing Library](https://testing-library.com/docs/dom-testing-library/intro/).
When selecting by text it is best to use [`getByRole` or `findByRole`](https://testing-library.com/docs/dom-testing-library/api-queries/#byrole)
as these enforce accessibility best practices as well. The examples below demonstrate the order of preference.
When writing Vue component unit tests, it can be wise to query children by component, so that the unit test can focus on comprehensive value coverage
......
---
redirect_to: 'https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/'
redirect_to: 'https://about.gitlab.com/handbook/marketing/strategic-marketing/roles-personas/'
---
This document was moved to [another location](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/).
This document was moved to [another location](https://about.gitlab.com/handbook/marketing/strategic-marketing/roles-personas/).
<!-- This redirect file can be deleted after February 1, 2021. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
......@@ -49,7 +49,7 @@ prompt, terminal, and command line) of your preference. Here are some suggestion
- For macOS users:
- Built-in: [Terminal](https://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line). Press <kbd>⌘ command</kbd> + <kbd>space</kbd> and type "terminal" to find it.
- [iTerm2](https://www.iterm2.com/), which you can integrate with [zsh](https://git-scm.com/book/id/v2/Appendix-A%3A-Git-in-Other-Environments-Git-in-Zsh) and [oh my zsh](https://ohmyz.sh/) for color highlighting, among other handy features for Git users.
- [iTerm2](https://iterm2.com/), which you can integrate with [zsh](https://git-scm.com/book/id/v2/Appendix-A%3A-Git-in-Other-Environments-Git-in-Zsh) and [oh my zsh](https://ohmyz.sh/) for color highlighting, among other handy features for Git users.
- For Windows users:
- Built-in: **cmd**. Click the search icon on the bottom navbar on Windows and type "cmd" to find it.
- [PowerShell](https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/install/installing-windows-powershell?view=powershell-7): a Windows "powered up" shell, from which you can execute a greater number of commands.
......
......@@ -27,7 +27,7 @@ For a real use case, read the blog post [Continuous integration: From Jenkins to
Moving from a traditional CI plug-in to a single application for the entire software development
life cycle can decrease hours spent on maintaining toolchains by 10% or more. For more details, see
the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab.html).
the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab/).
NOTE: **Note:**
This documentation focuses only on how to **configure** a Jenkins *integration* with
......
......@@ -232,7 +232,7 @@ Potential resolutions:
- If you're using GitLab Core or GitLab Starter, be sure you're using
GitLab 13.4 or later.
[Contact GitLab Support](https://about.gitlab.com/support) if none of these reasons apply.
[Contact GitLab Support](https://about.gitlab.com/support/) if none of these reasons apply.
#### Fixing synchronization issues
......@@ -245,11 +245,11 @@ resynchronize the information. To do so:
1. For each project, there's a sync button displayed next to the **last activity** date.
To perform a *soft resync*, click the button, or complete a *full sync* by shift clicking
the button. For more information, see
[Atlassian's documentation](https://confluence.atlassian.com/adminjiracloud/synchronize-an-account-972332890.html).
[Atlassian's documentation](https://support.atlassian.com/jira-cloud-administration/docs/synchronize-jira-cloud-to-bitbucket/).
### GitLab for Jira app
You can integrate GitLab.com and Jira Cloud using the [GitLab for Jira](https://marketplace.atlassian.com/apps/1221011/gitlab-for-jira) app in the Atlassian Marketplace.
You can integrate GitLab.com and Jira Cloud using the [GitLab for Jira](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud) app in the Atlassian Marketplace.
This method is recommended when using GitLab.com and Jira Cloud because data is synchronized in realtime, while the DVCS connector updates data only once per hour. If you are not using both of these environments, use the [Jira DVCS Connector](#jira-dvcs-configuration) method.
......@@ -257,7 +257,7 @@ This method is recommended when using GitLab.com and Jira Cloud because data is
For a walkthrough of the integration with GitLab for Jira, watch [Configure GitLab Jira Integration using Marketplace App](https://youtu.be/SwR-g1s1zTo) on YouTube.
1. Go to **Jira Settings > Apps > Find new apps**, then search for GitLab.
1. Click **GitLab for Jira**, then click **Get it now**. Or go the [App in the marketplace directly](https://marketplace.atlassian.com/apps/1221011/gitlab-for-jira)
1. Click **GitLab for Jira**, then click **Get it now**. Or go the [App in the marketplace directly](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud)
![Install GitLab App on Jira](img/jira_dev_panel_setup_com_1.png)
1. After installing, click **Get started** to go to the configurations page. This page is always available under **Jira Settings > Apps > Manage apps**.
......
......@@ -13,13 +13,13 @@ GitLab can integrate with [Kerberos](https://web.mit.edu/kerberos/) as an authen
[Kerberos](https://web.mit.edu/kerberos/) is a secure method for authenticating a request for a service in a
computer network. Kerberos was developed in the Athena Project at the
[Massachusetts Institute of Technology (MIT)](http://web.mit.edu/). The name is taken from Greek
[Massachusetts Institute of Technology (MIT)](https://web.mit.edu/). The name is taken from Greek
mythology; Kerberos was a three-headed dog who guarded the gates of Hades.
## Use-cases
- GitLab can be configured to allow your users to sign with their Kerberos credentials.
- You can use Kerberos to [prevent](http://web.mit.edu/sipb/doc/working/guide/guide/node20.html) anyone from intercepting or eavesdropping on the transmitted password.
- You can use Kerberos to [prevent](https://web.mit.edu/sipb/doc/working/guide/guide/node20.html) anyone from intercepting or eavesdropping on the transmitted password.
## Configuration
......
......@@ -173,7 +173,7 @@ Find more information how to apply and renew at
## GitLab for Open Source subscriptions
All [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/program/)
All [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/join/)
requests, including subscription renewals, must be made by using the application process.
If you have any questions, send an email to `opensource@gitlab.com` for assistance.
......
......@@ -168,7 +168,7 @@ application will be monitored by the WAF automatically.
Now we can make sure that Ingress is running properly with ModSecurity and send
a request to ensure our application is responding correctly. You must connect to
your cluster either using [Cloud Shell](https://cloud.google.com/shell/) or the [Google Cloud SDK](https://cloud.google.com/sdk/install).
your cluster either using [Cloud Shell](https://cloud.google.com/shell/) or the [Google Cloud SDK](https://cloud.google.com/sdk/docs/install).
1. After connecting to your cluster, check if the Ingress-NGINX controller is running and ModSecurity is enabled.
......
......@@ -143,7 +143,7 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
### 3.1. DevOps
1. [XebiaLabs: DevOps Terminology](https://xebialabs.com/glossary/)
1. [XebiaLabs: DevOps Terminology](https://digital.ai/glossary)
1. [XebiaLabs: Periodic Table of DevOps Tools](https://digital.ai/periodic-table-of-devops-tools)
1. [Puppet Labs: State of DevOps 2016 - Book](https://puppet.com/resources/report/2016-state-devops-report/)
......
......@@ -37,7 +37,7 @@ The table is sorted by:
## Use cases
This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#delaney-development-team-lead)
This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/strategic-marketing/roles-personas/#delaney-development-team-lead)
and others who want to understand broad code review dynamics, and identify patterns to explain them.
You can use Code Review Analytics to:
......
......@@ -23,7 +23,7 @@ To access Merge Request Analytics, from your project's menu, go to **Analytics >
## Use cases
This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#delaney-development-team-lead)
This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/strategic-marketing/roles-personas/#delaney-development-team-lead)
and others who want to understand broad patterns in code review and productivity.
You can use Merge Request Analytics to expose when your team is most and least productive, and
......
......@@ -155,7 +155,7 @@ You can use various tools to generate HAR files:
- [Fiddler](https://www.telerik.com/fiddler): Web debugging proxy
- [Insomnia Core](https://insomnia.rest/): API client
- [Chrome](https://www.google.com/chrome): Browser
- [Chrome](https://www.google.com/chrome/): Browser
- [Firefox](https://www.mozilla.org/en-US/firefox/): Browser
DANGER: **Warning:**
......
......@@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41203) in GitLab 13.4, only for public projects on GitLab.com.
As part of [GitLab's role as a CVE Numbering Authority](https://about.gitlab.com/security/cve)
As part of [GitLab's role as a CVE Numbering Authority](https://about.gitlab.com/security/cve/)
([CNA](https://cve.mitre.org/cve/cna.html)), you may request
[CVE](https://cve.mitre.org/index.html) identifiers from GitLab to track
vulnerabilities found within your project.
......
......@@ -274,13 +274,13 @@ to specify its location.
### Configuring NPM projects
You can configure NPM projects by using an [`.npmrc`](https://docs.npmjs.com/configuring-npm/npmrc.html)
You can configure NPM projects by using an [`.npmrc`](https://docs.npmjs.com/configuring-npm/npmrc.html/)
file.
#### Using private NPM registries
If you have a private NPM registry you can use the
[`registry`](https://docs.npmjs.com/using-npm/config#registry)
[`registry`](https://docs.npmjs.com/using-npm/config/#registry)
setting to specify its location.
For example:
......@@ -294,7 +294,7 @@ registry = https://npm.example.com
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [environment variable](#available-variables).
To disable TLS verification you can provide the [`strict-ssl`](https://docs.npmjs.com/using-npm/config#strict-ssl)
To disable TLS verification you can provide the [`strict-ssl`](https://docs.npmjs.com/using-npm/config/#strict-ssl)
setting.
For example:
......
......@@ -263,7 +263,7 @@ You can create a spreadsheet template to manage a pattern of consistently repeat
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an introduction to epic templates, see [GitLab Epics and Epic Template Tip](https://www.youtube.com/watch?v=D74xKFNw8vg).
For more on epic templates, see [Epic Templates - Repeatable sets of issues](https://about.gitlab.com/handbook/marketing/product-marketing/getting-started/104/).
For more on epic templates, see [Epic Templates - Repeatable sets of issues](https://about.gitlab.com/handbook/marketing/strategic-marketing/getting-started/104/).
## Manage multi-level child epics **(ULTIMATE)**
......
......@@ -22,7 +22,7 @@ We encourage you to view this document as [rendered by GitLab itself](https://gi
GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification](https://spec.commonmark.org/current/)
(which is based on standard Markdown) in several ways to add additional useful functionality.
It was inspired by [GitHub Flavored Markdown](https://docs.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).
It was inspired by [GitHub Flavored Markdown](https://docs.github.com/en/free-pro-team@latest/github/writing-on-github/basic-writing-and-formatting-syntax).
You can use GFM in the following areas:
......
......@@ -70,7 +70,10 @@ so that anyone who can access the project can use the package as a dependency.
Prerequisites:
- A package in a GitLab repository.
- A package in a GitLab repository. Composer packages should be versioned based on
the [Composer specification](https://getcomposer.org/doc/04-schema.md#version).
If the version is not valid, for example, it has three dots (`1.0.0.0`), an
error (`Validation failed: Version is invalid`) occurs when you publish.
- A valid `composer.json` file.
- The Packages feature is enabled in a GitLab repository.
- The project ID, which is on the project's home page.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment