Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
038366a093
commit
98d7cc758f
|
@ -226,7 +226,7 @@ export default {
|
|||
<a
|
||||
ref="titleWrapper"
|
||||
:v-once="!viewDiffsFileByFile"
|
||||
class="gl-mr-2 gl-text-decoration-none!"
|
||||
class="gl-mr-2 gl-text-decoration-none! gl-text-truncate"
|
||||
:href="titleLink"
|
||||
@click="handleFileNameClick"
|
||||
>
|
||||
|
|
|
@ -1,33 +1,29 @@
|
|||
<script>
|
||||
import { isEmpty } from 'lodash';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { GlLink, GlButton, GlIcon } from '@gitlab/ui';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import { GlButton, GlIcon, GlLink } from '@gitlab/ui';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
|
||||
import DetailRow from './sidebar_detail_row.vue';
|
||||
import ArtifactsBlock from './artifacts_block.vue';
|
||||
import TriggerBlock from './trigger_block.vue';
|
||||
import CommitBlock from './commit_block.vue';
|
||||
import StagesDropdown from './stages_dropdown.vue';
|
||||
import JobsContainer from './jobs_container.vue';
|
||||
import SidebarJobDetailsContainer from './sidebar_job_details_container.vue';
|
||||
|
||||
export default {
|
||||
name: 'JobSidebar',
|
||||
components: {
|
||||
ArtifactsBlock,
|
||||
CommitBlock,
|
||||
DetailRow,
|
||||
GlIcon,
|
||||
TriggerBlock,
|
||||
StagesDropdown,
|
||||
JobsContainer,
|
||||
GlLink,
|
||||
GlButton,
|
||||
SidebarJobDetailsContainer,
|
||||
TooltipOnTruncate,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
artifactHelpUrl: {
|
||||
type: String,
|
||||
|
@ -42,53 +38,12 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState(['job', 'stages', 'jobs', 'selectedStage']),
|
||||
coverage() {
|
||||
return `${this.job.coverage}%`;
|
||||
},
|
||||
duration() {
|
||||
return timeIntervalInWords(this.job.duration);
|
||||
},
|
||||
queued() {
|
||||
return timeIntervalInWords(this.job.queued);
|
||||
},
|
||||
runnerId() {
|
||||
return `${this.job.runner.description} (#${this.job.runner.id})`;
|
||||
},
|
||||
retryButtonClass() {
|
||||
let className = 'js-retry-button btn btn-retry';
|
||||
className +=
|
||||
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
|
||||
return className;
|
||||
},
|
||||
hasTimeout() {
|
||||
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
|
||||
},
|
||||
timeout() {
|
||||
if (this.job.metadata == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let t = this.job.metadata.timeout_human_readable;
|
||||
if (this.job.metadata.timeout_source !== '') {
|
||||
t += sprintf(__(` (from %{timeoutSource})`), {
|
||||
timeoutSource: this.job.metadata.timeout_source,
|
||||
});
|
||||
}
|
||||
|
||||
return t;
|
||||
},
|
||||
renderBlock() {
|
||||
return (
|
||||
this.job.duration ||
|
||||
this.job.finished_at ||
|
||||
this.job.erased_at ||
|
||||
this.job.queued ||
|
||||
this.hasTimeout ||
|
||||
this.job.runner ||
|
||||
this.job.coverage ||
|
||||
this.job.tags.length
|
||||
);
|
||||
},
|
||||
hasArtifact() {
|
||||
return !isEmpty(this.job.artifact);
|
||||
},
|
||||
|
@ -96,16 +51,10 @@ export default {
|
|||
return !isEmpty(this.job.trigger);
|
||||
},
|
||||
hasStages() {
|
||||
return (
|
||||
(this.job &&
|
||||
this.job.pipeline &&
|
||||
this.job.pipeline.stages &&
|
||||
this.job.pipeline.stages.length > 0) ||
|
||||
false
|
||||
);
|
||||
return this.job?.pipeline?.stages?.length > 0;
|
||||
},
|
||||
commit() {
|
||||
return this.job.pipeline && this.job.pipeline.commit ? this.job.pipeline.commit : {};
|
||||
return this.job?.pipeline?.commit || {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -131,22 +80,22 @@ export default {
|
|||
data-method="post"
|
||||
data-qa-selector="retry_button"
|
||||
rel="nofollow"
|
||||
>{{ __('Retry') }}</gl-link
|
||||
>
|
||||
>{{ __('Retry') }}
|
||||
</gl-link>
|
||||
<gl-link
|
||||
v-if="job.cancel_path"
|
||||
:href="job.cancel_path"
|
||||
class="js-cancel-job btn btn-default"
|
||||
data-method="post"
|
||||
rel="nofollow"
|
||||
>{{ __('Cancel') }}</gl-link
|
||||
>
|
||||
>{{ __('Cancel') }}
|
||||
</gl-link>
|
||||
</div>
|
||||
|
||||
<gl-button
|
||||
:aria-label="__('Toggle Sidebar')"
|
||||
class="d-md-none gl-ml-2 js-sidebar-build-toggle"
|
||||
category="tertiary"
|
||||
class="gl-display-md-none gl-ml-2 js-sidebar-build-toggle"
|
||||
icon="chevron-double-lg-right"
|
||||
@click="toggleSidebar"
|
||||
/>
|
||||
|
@ -158,77 +107,37 @@ export default {
|
|||
:href="job.new_issue_path"
|
||||
class="btn btn-success btn-inverted float-left mr-2"
|
||||
data-testid="job-new-issue"
|
||||
>{{ __('New issue') }}</gl-link
|
||||
>
|
||||
>{{ __('New issue') }}
|
||||
</gl-link>
|
||||
<gl-link
|
||||
v-if="job.terminal_path"
|
||||
:href="job.terminal_path"
|
||||
class="js-terminal-link btn btn-primary btn-inverted visible-md-block visible-lg-block float-left"
|
||||
target="_blank"
|
||||
>
|
||||
{{ __('Debug') }} <gl-icon name="external-link" :size="14" />
|
||||
{{ __('Debug') }}
|
||||
<gl-icon :size="14" name="external-link" />
|
||||
</gl-link>
|
||||
</div>
|
||||
|
||||
<div v-if="renderBlock" class="block">
|
||||
<detail-row
|
||||
v-if="job.duration"
|
||||
:value="duration"
|
||||
class="js-job-duration"
|
||||
title="Duration"
|
||||
/>
|
||||
<detail-row
|
||||
v-if="job.finished_at"
|
||||
:value="timeFormatted(job.finished_at)"
|
||||
class="js-job-finished"
|
||||
title="Finished"
|
||||
/>
|
||||
<detail-row
|
||||
v-if="job.erased_at"
|
||||
:value="timeFormatted(job.erased_at)"
|
||||
class="js-job-erased"
|
||||
title="Erased"
|
||||
/>
|
||||
<detail-row v-if="job.queued" :value="queued" class="js-job-queued" title="Queued" />
|
||||
<detail-row
|
||||
v-if="hasTimeout"
|
||||
:help-url="runnerHelpUrl"
|
||||
:value="timeout"
|
||||
class="js-job-timeout"
|
||||
title="Timeout"
|
||||
/>
|
||||
<detail-row v-if="job.runner" :value="runnerId" class="js-job-runner" title="Runner" />
|
||||
<detail-row
|
||||
v-if="job.coverage"
|
||||
:value="coverage"
|
||||
class="js-job-coverage"
|
||||
title="Coverage"
|
||||
/>
|
||||
<p v-if="job.tags.length" class="build-detail-row js-job-tags">
|
||||
<span class="font-weight-bold">{{ __('Tags:') }}</span>
|
||||
<span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{
|
||||
tag
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<sidebar-job-details-container :runner-help-url="runnerHelpUrl" />
|
||||
<artifacts-block v-if="hasArtifact" :artifact="job.artifact" :help-url="artifactHelpUrl" />
|
||||
<trigger-block v-if="hasTriggers" :trigger="job.trigger" />
|
||||
<commit-block
|
||||
:is-last-block="hasStages"
|
||||
:commit="commit"
|
||||
:is-last-block="hasStages"
|
||||
:merge-request="job.merge_request"
|
||||
/>
|
||||
|
||||
<stages-dropdown
|
||||
:stages="stages"
|
||||
v-if="job.pipeline"
|
||||
:pipeline="job.pipeline"
|
||||
:selected-stage="selectedStage"
|
||||
:stages="stages"
|
||||
@requestSidebarStageDropdown="fetchJobsForStage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<jobs-container v-if="jobs.length" :jobs="jobs" :job-id="job.id" />
|
||||
<jobs-container v-if="jobs.length" :job-id="job.id" :jobs="jobs" />
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import DetailRow from './sidebar_detail_row.vue';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
|
||||
|
||||
export default {
|
||||
name: 'SidebarJobDetailsContainer',
|
||||
components: {
|
||||
DetailRow,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
runnerHelpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['job']),
|
||||
coverage() {
|
||||
return `${this.job.coverage}%`;
|
||||
},
|
||||
duration() {
|
||||
return timeIntervalInWords(this.job.duration);
|
||||
},
|
||||
erasedAt() {
|
||||
return this.timeFormatted(this.job.erased_at);
|
||||
},
|
||||
finishedAt() {
|
||||
return this.timeFormatted(this.job.finished_at);
|
||||
},
|
||||
hasTags() {
|
||||
return this.job?.tags?.length;
|
||||
},
|
||||
hasTimeout() {
|
||||
return this.job?.metadata?.timeout_human_readable ?? false;
|
||||
},
|
||||
hasAnyDetail() {
|
||||
return Boolean(
|
||||
this.job.duration ||
|
||||
this.job.finished_at ||
|
||||
this.job.erased_at ||
|
||||
this.job.queued ||
|
||||
this.job.runner ||
|
||||
this.job.coverage,
|
||||
);
|
||||
},
|
||||
queued() {
|
||||
return timeIntervalInWords(this.job.queued);
|
||||
},
|
||||
runnerId() {
|
||||
return `${this.job.runner.description} (#${this.job.runner.id})`;
|
||||
},
|
||||
shouldRenderBlock() {
|
||||
return Boolean(this.hasAnyDetail || this.hasTimeout || this.hasTags);
|
||||
},
|
||||
timeout() {
|
||||
return `${this.job?.metadata?.timeout_human_readable}${this.timeoutSource}`;
|
||||
},
|
||||
timeoutSource() {
|
||||
if (!this.job?.metadata?.timeout_source) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf(__(` (from %{timeoutSource})`), {
|
||||
timeoutSource: this.job.metadata.timeout_source,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="shouldRenderBlock" class="block">
|
||||
<detail-row v-if="job.duration" :value="duration" title="Duration" />
|
||||
<detail-row
|
||||
v-if="job.finished_at"
|
||||
:value="finishedAt"
|
||||
data-testid="job-finished"
|
||||
title="Finished"
|
||||
/>
|
||||
<detail-row v-if="job.erased_at" :value="erasedAt" title="Erased" />
|
||||
<detail-row v-if="job.queued" :value="queued" title="Queued" />
|
||||
<detail-row
|
||||
v-if="hasTimeout"
|
||||
:help-url="runnerHelpUrl"
|
||||
:value="timeout"
|
||||
data-testid="job-timeout"
|
||||
title="Timeout"
|
||||
/>
|
||||
<detail-row v-if="job.runner" :value="runnerId" title="Runner" />
|
||||
<detail-row v-if="job.coverage" :value="coverage" title="Coverage" />
|
||||
|
||||
<p v-if="hasTags" class="build-detail-row" data-testid="job-tags">
|
||||
<span class="font-weight-bold">{{ __('Tags:') }}</span>
|
||||
<span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{ tag }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
|
@ -4,7 +4,7 @@ import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
|
|||
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
|
||||
|
||||
export const hasUnmetPrerequisitesFailure = state =>
|
||||
state.job && state.job.failure_reason && state.job.failure_reason === 'unmet_prerequisites';
|
||||
state?.job?.failure_reason === 'unmet_prerequisites';
|
||||
|
||||
export const shouldRenderCalloutMessage = state =>
|
||||
!isEmpty(state.job.status) && !isEmpty(state.job.callout_message);
|
||||
|
|
|
@ -3,5 +3,5 @@ import initSearchApp from '~/search';
|
|||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initSearchApp();
|
||||
return new Search();
|
||||
return new Search(); // Deprecated Dropdown (Projects)
|
||||
});
|
||||
|
|
|
@ -5,48 +5,22 @@ import { deprecatedCreateFlash as Flash } from '~/flash';
|
|||
import Api from '~/api';
|
||||
import { __ } from '~/locale';
|
||||
import Project from '~/pages/projects/project';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import { visitUrl, queryToObject } from '~/lib/utils/url_utility';
|
||||
import refreshCounts from './refresh_counts';
|
||||
|
||||
export default class Search {
|
||||
constructor() {
|
||||
setHighlightClass();
|
||||
const $groupDropdown = $('.js-search-group-dropdown');
|
||||
setHighlightClass(); // Code Highlighting
|
||||
const $projectDropdown = $('.js-search-project-dropdown');
|
||||
|
||||
this.searchInput = '.js-search-input';
|
||||
this.searchClear = '.js-search-clear';
|
||||
|
||||
this.groupId = $groupDropdown.data('groupId');
|
||||
const query = queryToObject(window.location.search);
|
||||
this.groupId = query?.group_id;
|
||||
this.eventListeners();
|
||||
refreshCounts();
|
||||
|
||||
initDeprecatedJQueryDropdown($groupDropdown, {
|
||||
selectable: true,
|
||||
filterable: true,
|
||||
filterRemote: true,
|
||||
fieldName: 'group_id',
|
||||
search: {
|
||||
fields: ['full_name'],
|
||||
},
|
||||
data(term, callback) {
|
||||
return Api.groups(term, {}, data => {
|
||||
data.unshift({
|
||||
full_name: __('Any'),
|
||||
});
|
||||
data.splice(1, 0, { type: 'divider' });
|
||||
return callback(data);
|
||||
});
|
||||
},
|
||||
id(obj) {
|
||||
return obj.id;
|
||||
},
|
||||
text(obj) {
|
||||
return obj.full_name;
|
||||
},
|
||||
clicked: () => Search.submitSearch(),
|
||||
});
|
||||
|
||||
initDeprecatedJQueryDropdown($projectDropdown, {
|
||||
selectable: true,
|
||||
filterable: true,
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<script>
|
||||
import {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlSearchBoxByType,
|
||||
GlLoadingIcon,
|
||||
GlIcon,
|
||||
GlSkeletonLoader,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
|
||||
import { ANY_GROUP, GROUP_QUERY_PARAM, PROJECT_QUERY_PARAM } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'GroupFilter',
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlSearchBoxByType,
|
||||
GlLoadingIcon,
|
||||
GlIcon,
|
||||
GlSkeletonLoader,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
initialGroup: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
groupSearch: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['groups', 'fetchingGroups']),
|
||||
selectedGroup: {
|
||||
get() {
|
||||
return isEmpty(this.initialGroup) ? ANY_GROUP : this.initialGroup;
|
||||
},
|
||||
set(group) {
|
||||
visitUrl(setUrlParams({ [GROUP_QUERY_PARAM]: group.id, [PROJECT_QUERY_PARAM]: null }));
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchGroups']),
|
||||
isGroupSelected(group) {
|
||||
return group.id === this.selectedGroup.id;
|
||||
},
|
||||
handleGroupChange(group) {
|
||||
this.selectedGroup = group;
|
||||
},
|
||||
},
|
||||
ANY_GROUP,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-dropdown
|
||||
ref="groupFilter"
|
||||
class="gl-w-full"
|
||||
menu-class="gl-w-full!"
|
||||
toggle-class="gl-text-truncate gl-reset-line-height!"
|
||||
:header-text="__('Filter results by group')"
|
||||
@show="fetchGroups(groupSearch)"
|
||||
>
|
||||
<template #button-content>
|
||||
<span class="dropdown-toggle-text gl-flex-grow-1 gl-text-truncate">
|
||||
{{ selectedGroup.name }}
|
||||
</span>
|
||||
<gl-loading-icon v-if="fetchingGroups" inline class="mr-2" />
|
||||
<gl-icon
|
||||
v-if="!isGroupSelected($options.ANY_GROUP)"
|
||||
v-gl-tooltip
|
||||
name="clear"
|
||||
:title="__('Clear')"
|
||||
class="gl-text-gray-200! gl-hover-text-blue-800!"
|
||||
@click.stop="handleGroupChange($options.ANY_GROUP)"
|
||||
/>
|
||||
<gl-icon name="chevron-down" />
|
||||
</template>
|
||||
<div class="gl-sticky gl-top-0 gl-z-index-1 gl-bg-white">
|
||||
<gl-search-box-by-type
|
||||
v-model="groupSearch"
|
||||
class="m-2"
|
||||
:debounce="500"
|
||||
@input="fetchGroups"
|
||||
/>
|
||||
<gl-dropdown-item
|
||||
class="gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2! gl-mb-2"
|
||||
:is-check-item="true"
|
||||
:is-checked="isGroupSelected($options.ANY_GROUP)"
|
||||
@click="handleGroupChange($options.ANY_GROUP)"
|
||||
>
|
||||
{{ $options.ANY_GROUP.name }}
|
||||
</gl-dropdown-item>
|
||||
</div>
|
||||
<div v-if="!fetchingGroups">
|
||||
<gl-dropdown-item
|
||||
v-for="group in groups"
|
||||
:key="group.id"
|
||||
:is-check-item="true"
|
||||
:is-checked="isGroupSelected(group)"
|
||||
@click="handleGroupChange(group)"
|
||||
>
|
||||
{{ group.full_name }}
|
||||
</gl-dropdown-item>
|
||||
</div>
|
||||
<div v-if="fetchingGroups" class="mx-3 mt-2">
|
||||
<gl-skeleton-loader :height="100">
|
||||
<rect y="0" width="90%" height="20" rx="4" />
|
||||
<rect y="40" width="70%" height="20" rx="4" />
|
||||
<rect y="80" width="80%" height="20" rx="4" />
|
||||
</gl-skeleton-loader>
|
||||
</div>
|
||||
</gl-dropdown>
|
||||
</template>
|
|
@ -0,0 +1,10 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export const ANY_GROUP = Object.freeze({
|
||||
id: null,
|
||||
name: __('Any'),
|
||||
});
|
||||
|
||||
export const GROUP_QUERY_PARAM = 'group_id';
|
||||
|
||||
export const PROJECT_QUERY_PARAM = 'project_id';
|
|
@ -0,0 +1,28 @@
|
|||
import Vue from 'vue';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import GroupFilter from './components/group_filter.vue';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
export default store => {
|
||||
let initialGroup;
|
||||
const el = document.getElementById('js-search-group-dropdown');
|
||||
|
||||
const { initialGroupData } = el.dataset;
|
||||
|
||||
initialGroup = JSON.parse(initialGroupData);
|
||||
initialGroup = convertObjectPropsToCamelCase(initialGroup, { deep: true });
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
store,
|
||||
render(createElement) {
|
||||
return createElement(GroupFilter, {
|
||||
props: {
|
||||
initialGroup,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
import { queryToObject } from '~/lib/utils/url_utility';
|
||||
import createStore from './store';
|
||||
import initDropdownFilters from './dropdown_filter';
|
||||
import initGroupFilter from './group_filter';
|
||||
|
||||
export default () => {
|
||||
const store = createStore({ query: queryToObject(window.location.search) });
|
||||
|
||||
initDropdownFilters(store);
|
||||
initGroupFilter(store);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import Api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
export const fetchGroups = ({ commit }, search) => {
|
||||
commit(types.REQUEST_GROUPS);
|
||||
Api.groups(search)
|
||||
.then(data => {
|
||||
commit(types.RECEIVE_GROUPS_SUCCESS, data);
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({ message: __('There was a problem fetching groups.') });
|
||||
commit(types.RECEIVE_GROUPS_ERROR);
|
||||
});
|
||||
};
|
|
@ -1,10 +1,14 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import * as actions from './actions';
|
||||
import mutations from './mutations';
|
||||
import createState from './state';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export const getStoreConfig = ({ query }) => ({
|
||||
actions,
|
||||
mutations,
|
||||
state: createState({ query }),
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const REQUEST_GROUPS = 'REQUEST_GROUPS';
|
||||
export const RECEIVE_GROUPS_SUCCESS = 'RECEIVE_GROUPS_SUCCESS';
|
||||
export const RECEIVE_GROUPS_ERROR = 'RECEIVE_GROUPS_ERROR';
|
|
@ -0,0 +1,15 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.REQUEST_GROUPS](state) {
|
||||
state.fetchingGroups = true;
|
||||
},
|
||||
[types.RECEIVE_GROUPS_SUCCESS](state, data) {
|
||||
state.fetchingGroups = false;
|
||||
state.groups = data;
|
||||
},
|
||||
[types.RECEIVE_GROUPS_ERROR](state) {
|
||||
state.fetchingGroups = false;
|
||||
state.groups = [];
|
||||
},
|
||||
};
|
|
@ -1,4 +1,6 @@
|
|||
const createState = ({ query }) => ({
|
||||
query,
|
||||
groups: [],
|
||||
fetchingGroups: false,
|
||||
});
|
||||
export default createState;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { DEFAULT_IMAGE_UPLOAD_PATH } from '../constants';
|
|||
import imageRepository from '../image_repository';
|
||||
import formatter from '../services/formatter';
|
||||
import templater from '../services/templater';
|
||||
import renderImage from '../services/renderers/render_image';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -41,6 +42,10 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
project: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
imageRoot: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -72,6 +77,12 @@ export default {
|
|||
isWysiwygMode() {
|
||||
return this.editorMode === EDITOR_TYPES.wysiwyg;
|
||||
},
|
||||
customRenderers() {
|
||||
const imageRenderer = renderImage.build(this.mounts, this.project);
|
||||
return {
|
||||
image: [imageRenderer],
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.refreshEditHelpers();
|
||||
|
@ -140,6 +151,7 @@ export default {
|
|||
:content="editableContent"
|
||||
:initial-edit-type="editorMode"
|
||||
:image-root="imageRoot"
|
||||
:options="{ customRenderers }"
|
||||
class="mb-9 pb-6 h-100"
|
||||
@modeChange="onModeChange"
|
||||
@input="onInputChange"
|
||||
|
|
|
@ -139,6 +139,7 @@ export default {
|
|||
:saving-changes="isSavingChanges"
|
||||
:return-url="appData.returnUrl"
|
||||
:mounts="appData.mounts"
|
||||
:project="appData.project"
|
||||
@submit="onPrepareSubmit"
|
||||
/>
|
||||
<edit-meta-modal
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
const canRender = ({ type }) => type === 'image';
|
||||
|
||||
// NOTE: the `metadata` is not used yet, but will be used in a follow-up iteration
|
||||
// To be removed with the next iteration of https://gitlab.com/gitlab-org/gitlab/-/issues/218531
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let metadata;
|
||||
|
||||
const render = (node, { skipChildren }) => {
|
||||
skipChildren();
|
||||
|
||||
// To be removed with the next iteration of https://gitlab.com/gitlab-org/gitlab/-/issues/218531
|
||||
// TODO resolve relative paths
|
||||
|
||||
return {
|
||||
type: 'openTag',
|
||||
tagName: 'img',
|
||||
selfClose: true,
|
||||
attributes: {
|
||||
src: node.destination,
|
||||
alt: node.firstChild.literal,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const build = (mounts, project) => {
|
||||
metadata = { mounts, project };
|
||||
return { canRender, render };
|
||||
};
|
||||
|
||||
export default { build };
|
|
@ -270,7 +270,8 @@ input[type='checkbox']:hover {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-menu-toggle {
|
||||
.dropdown-menu-toggle,
|
||||
.gl-new-dropdown {
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: 240px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserGroupsCounter
|
||||
def initialize(user_ids)
|
||||
@user_ids = user_ids
|
||||
end
|
||||
|
||||
def execute
|
||||
Namespace.unscoped do
|
||||
Namespace.from_union([
|
||||
groups,
|
||||
project_groups
|
||||
]).group(:user_id).count # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :user_ids
|
||||
|
||||
def groups
|
||||
Group.for_authorized_group_members(user_ids)
|
||||
.select('namespaces.*, members.user_id as user_id')
|
||||
end
|
||||
|
||||
def project_groups
|
||||
Group.for_authorized_project_members(user_ids)
|
||||
.select('namespaces.*, project_authorizations.user_id as user_id')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Todos
|
||||
class Create < ::Mutations::Todos::Base
|
||||
graphql_name 'TodoCreate'
|
||||
|
||||
authorize :create_todo
|
||||
|
||||
argument :target_id,
|
||||
Types::GlobalIDType[Todoable],
|
||||
required: true,
|
||||
description: "The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported"
|
||||
|
||||
field :todo, Types::TodoType,
|
||||
null: true,
|
||||
description: 'The to-do created'
|
||||
|
||||
def resolve(target_id:)
|
||||
id = ::Types::GlobalIDType[Todoable].coerce_isolated_input(target_id)
|
||||
target = authorized_find!(id)
|
||||
|
||||
todo = TodoService.new.mark_todo(target, current_user)&.first
|
||||
errors = errors_on_object(todo) if todo
|
||||
|
||||
{
|
||||
todo: todo,
|
||||
errors: errors
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_object(id)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,7 +23,6 @@ module Resolvers
|
|||
# The namespace could have been loaded in batch by `BatchLoader`.
|
||||
# At this point we need the `id` or the `full_path` of the namespace
|
||||
# to query for projects, so make sure it's loaded and not `nil` before continuing.
|
||||
namespace = object.respond_to?(:sync) ? object.sync : object
|
||||
return Project.none if namespace.nil?
|
||||
|
||||
query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route
|
||||
|
@ -41,6 +40,14 @@ module Resolvers
|
|||
complexity = super
|
||||
complexity + 10
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def namespace
|
||||
strong_memoize(:namespace) do
|
||||
object.respond_to?(:sync) ? object.sync : object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
module Users
|
||||
class GroupCountResolver < BaseResolver
|
||||
alias_method :user, :object
|
||||
|
||||
def resolve(**args)
|
||||
return unless can_read_group_count?
|
||||
|
||||
BatchLoader::GraphQL.for(user.id).batch do |user_ids, loader|
|
||||
results = UserGroupsCounter.new(user_ids).execute
|
||||
|
||||
results.each do |user_id, count|
|
||||
loader.call(user_id, count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def can_read_group_count?
|
||||
current_user&.can?(:read_group_count, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -63,6 +63,7 @@ module Types
|
|||
mount_mutation Mutations::Terraform::State::Delete
|
||||
mount_mutation Mutations::Terraform::State::Lock
|
||||
mount_mutation Mutations::Terraform::State::Unlock
|
||||
mount_mutation Mutations::Todos::Create
|
||||
mount_mutation Mutations::Todos::MarkDone
|
||||
mount_mutation Mutations::Todos::Restore
|
||||
mount_mutation Mutations::Todos::MarkAllDone
|
||||
|
|
|
@ -7,6 +7,7 @@ module Types
|
|||
description 'Values for sorting projects'
|
||||
|
||||
value 'SIMILARITY', 'Most similar to the search query', value: :similarity
|
||||
value 'STORAGE', 'Sort by storage size', value: :storage
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,10 @@ module Types
|
|||
field :group_memberships, Types::GroupMemberType.connection_type, null: true,
|
||||
description: 'Group memberships of the user',
|
||||
method: :group_members
|
||||
field :group_count, GraphQL::INT_TYPE, null: true,
|
||||
resolver: Resolvers::Users::GroupCountResolver,
|
||||
description: 'Group count for the user',
|
||||
feature_flag: :user_group_counts
|
||||
field :status, Types::UserStatusType, null: true,
|
||||
description: 'User status'
|
||||
field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
|
||||
|
|
|
@ -80,7 +80,7 @@ module Ci
|
|||
end
|
||||
|
||||
def fog_store_class
|
||||
if Feature.enabled?(:ci_trace_new_fog_store)
|
||||
if Feature.enabled?(:ci_trace_new_fog_store, default_enabled: true)
|
||||
Ci::BuildTraceChunks::Fog
|
||||
else
|
||||
Ci::BuildTraceChunks::LegacyFog
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Todoable concern
|
||||
#
|
||||
# Specify object types that supports todos.
|
||||
#
|
||||
# Used by Issue, MergeRequest, Design and Epic.
|
||||
#
|
||||
module Todoable
|
||||
end
|
|
@ -10,6 +10,7 @@ module DesignManagement
|
|||
include Mentionable
|
||||
include WhereComposite
|
||||
include RelativePositioning
|
||||
include Todoable
|
||||
|
||||
belongs_to :project, inverse_of: :designs
|
||||
belongs_to :issue
|
||||
|
|
|
@ -98,6 +98,17 @@ class Group < Namespace
|
|||
|
||||
scope :by_id, ->(groups) { where(id: groups) }
|
||||
|
||||
scope :for_authorized_group_members, -> (user_ids) do
|
||||
joins(:group_members)
|
||||
.where("members.user_id IN (?)", user_ids)
|
||||
.where("access_level >= ?", Gitlab::Access::GUEST)
|
||||
end
|
||||
|
||||
scope :for_authorized_project_members, -> (user_ids) do
|
||||
joins(projects: :project_authorizations)
|
||||
.where("project_authorizations.user_id IN (?)", user_ids)
|
||||
end
|
||||
|
||||
class << self
|
||||
def sort_by_attribute(method)
|
||||
if method == 'storage_size_desc'
|
||||
|
|
|
@ -21,6 +21,7 @@ class Issue < ApplicationRecord
|
|||
include IdInOrdered
|
||||
include Presentable
|
||||
include IssueAvailableFeatures
|
||||
include Todoable
|
||||
|
||||
DueDateStruct = Struct.new(:title, :name).freeze
|
||||
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
||||
|
|
|
@ -22,6 +22,7 @@ class MergeRequest < ApplicationRecord
|
|||
include StateEventable
|
||||
include ApprovableBase
|
||||
include IdInOrdered
|
||||
include Todoable
|
||||
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@ class IssuePolicy < IssuablePolicy
|
|||
rule { ~can?(:read_design) }.policy do
|
||||
prevent :move_design
|
||||
end
|
||||
|
||||
rule { ~anonymous & can?(:read_issue) }.policy do
|
||||
enable :create_todo
|
||||
end
|
||||
end
|
||||
|
||||
IssuePolicy.prepend_if_ee('EE::IssuePolicy')
|
||||
|
|
|
@ -14,6 +14,10 @@ class MergeRequestPolicy < IssuablePolicy
|
|||
rule { can?(:update_merge_request) }.policy do
|
||||
enable :approve_merge_request
|
||||
end
|
||||
|
||||
rule { ~anonymous & can?(:read_merge_request) }.policy do
|
||||
enable :create_todo
|
||||
end
|
||||
end
|
||||
|
||||
MergeRequestPolicy.prepend_if_ee('EE::MergeRequestPolicy')
|
||||
|
|
|
@ -21,6 +21,7 @@ class UserPolicy < BasePolicy
|
|||
enable :update_user
|
||||
enable :update_user_status
|
||||
enable :read_user_personal_access_tokens
|
||||
enable :read_group_count
|
||||
end
|
||||
|
||||
rule { default }.enable :read_user_profile
|
||||
|
|
|
@ -33,15 +33,17 @@ class BulkCreateIntegrationService
|
|||
klass.insert_all(items_to_insert, returning: [:id])
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def run_callbacks(batch)
|
||||
if integration.external_issue_tracker?
|
||||
batch.update_all(has_external_issue_tracker: true)
|
||||
Project.where(id: batch.select(:id)).update_all(has_external_issue_tracker: true)
|
||||
end
|
||||
|
||||
if integration.external_wiki?
|
||||
batch.update_all(has_external_wiki: true)
|
||||
Project.where(id: batch.select(:id)).update_all(has_external_wiki: true)
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def service_hash
|
||||
if integration.template?
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
- add_to_breadcrumbs _("Groups"), admin_groups_path
|
||||
- breadcrumb_title @group.name
|
||||
- page_title @group.name, _("Groups")
|
||||
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
|
||||
|
||||
.js-remove-member-modal
|
||||
%h3.page-title
|
||||
|
@ -116,7 +117,7 @@
|
|||
= select_tag :access_level, options_for_select(@group.access_level_roles), class: "project-access-select select2"
|
||||
%hr
|
||||
= button_tag _('Add users to group'), class: "gl-button btn btn-success"
|
||||
= render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
|
||||
= render 'shared/members/requests', membership_source: @group, group: @group, requesters: @requesters, force_mobile_view: true
|
||||
|
||||
.card
|
||||
.card-header
|
||||
|
@ -127,6 +128,11 @@
|
|||
= sprite_icon('pencil-square', css_class: 'gl-icon')
|
||||
= _('Manage access')
|
||||
%ul.content-list.group-users-list.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
|
||||
= render partial: 'shared/members/member',
|
||||
collection: @members, as: :member,
|
||||
locals: { membership_source: @group,
|
||||
group: @group,
|
||||
show_controls: false,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
.card-footer
|
||||
= paginate @members, param_name: 'members_page', theme: 'gitlab'
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
- breadcrumb_title @project.full_name
|
||||
- page_title @project.full_name, _("Projects")
|
||||
- @content_class = "admin-projects"
|
||||
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
|
||||
|
||||
.js-remove-member-modal
|
||||
%h3.page-title
|
||||
|
@ -183,11 +184,16 @@
|
|||
= sprite_icon('pencil-square', css_class: 'gl-icon')
|
||||
= _('Manage access')
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
|
||||
= render partial: 'shared/members/member',
|
||||
collection: @group_members, as: :member,
|
||||
locals: { membership_source: @project,
|
||||
group: @group,
|
||||
show_controls: false,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
.card-footer
|
||||
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
|
||||
|
||||
= render 'shared/members/requests', membership_source: @project, requesters: @requesters, force_mobile_view: true
|
||||
= render 'shared/members/requests', membership_source: @project, group: @group, requesters: @requesters, force_mobile_view: true
|
||||
|
||||
.card
|
||||
.card-header
|
||||
|
@ -199,6 +205,11 @@
|
|||
= sprite_icon('pencil-square', css_class: 'gl-icon')
|
||||
= _('Manage access')
|
||||
%ul.content-list.project_members.members-list
|
||||
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
|
||||
= render partial: 'shared/members/member',
|
||||
collection: @project_members, as: :member,
|
||||
locals: { membership_source: @project,
|
||||
group: @group,
|
||||
show_controls: false,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
.card-footer
|
||||
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
- show_access_requests = can_manage_members && @requesters.exists?
|
||||
- invited_active = params[:search_invited].present? || params[:invited_members_page].present?
|
||||
- vue_members_list_enabled = Feature.enabled?(:vue_group_members_list, @group)
|
||||
- current_user_is_group_owner = @group && @group.has_owner?(current_user)
|
||||
|
||||
- form_item_label_css_class = 'label-bold gl-mr-2 gl-mb-0 gl-py-2 align-self-md-center'
|
||||
|
||||
|
@ -71,7 +72,11 @@
|
|||
.js-group-members-list{ data: group_members_list_data_attributes(@group, @members) }
|
||||
- else
|
||||
%ul.content-list.members-list{ data: { qa_selector: 'members_list' } }
|
||||
= render partial: 'shared/members/member', collection: @members, as: :member
|
||||
= render partial: 'shared/members/member',
|
||||
collection: @members, as: :member,
|
||||
locals: { membership_source: @group,
|
||||
group: @group,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
= paginate @members, theme: 'gitlab', params: { invited_members_page: nil, search_invited: nil }
|
||||
- if @group.shared_with_group_links.any?
|
||||
#tab-groups.tab-pane
|
||||
|
@ -97,7 +102,11 @@
|
|||
.js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members) }
|
||||
- else
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: @invited_members, as: :member
|
||||
= render partial: 'shared/members/member',
|
||||
collection: @invited_members, as: :member,
|
||||
locals: { membership_source: @group,
|
||||
group: @group,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
= paginate @invited_members, param_name: 'invited_members_page', theme: 'gitlab', params: { page: nil }
|
||||
- if show_access_requests
|
||||
#tab-access-requests.tab-pane
|
||||
|
@ -109,4 +118,8 @@
|
|||
.js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) }
|
||||
- else
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: @requesters, as: :member
|
||||
= render partial: 'shared/members/member',
|
||||
collection: @requesters, as: :member,
|
||||
locals: { membership_source: @group,
|
||||
group: @group,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
- project = local_assigns.fetch(:project)
|
||||
- members = local_assigns.fetch(:members)
|
||||
- group = local_assigns.fetch(:group)
|
||||
- current_user_is_group_owner = group && group.has_owner?(current_user)
|
||||
|
||||
.card
|
||||
.card-header.flex-project-members-panel
|
||||
|
@ -15,4 +17,8 @@
|
|||
= label_tag :sort_by, _('Sort by'), class: 'col-form-label label-bold px-2'
|
||||
= render 'shared/members/sort_dropdown'
|
||||
%ul.content-list.members-list{ data: { qa_selector: 'members_list' } }
|
||||
= render partial: 'shared/members/member', collection: members, as: :member
|
||||
= render partial: 'shared/members/member',
|
||||
collection: members, as: :member,
|
||||
locals: { membership_source: project,
|
||||
group: group,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- page_title _("Members")
|
||||
- can_admin_project_members = can?(current_user, :admin_project_member, @project)
|
||||
- group = @project.group
|
||||
|
||||
.js-remove-member-modal
|
||||
.row.gl-mt-3
|
||||
|
@ -32,12 +33,12 @@
|
|||
- elsif @project.allowed_to_share_with_group?
|
||||
.invite-group= render 'shared/members/invite_group', access_levels: ProjectGroupLink.access_options, default_access_level: ProjectGroupLink.default_access, submit_url: project_group_links_path(@project), group_link_field: 'link_group_id', group_access_field: 'link_group_access'
|
||||
|
||||
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
|
||||
= render 'shared/members/requests', membership_source: @project, group: group, requesters: @requesters
|
||||
.clearfix
|
||||
%h5.member.existing-title
|
||||
= _("Existing members and groups")
|
||||
- if @group_links.any?
|
||||
= render 'projects/project_members/groups', group_links: @group_links
|
||||
|
||||
= render 'projects/project_members/team', project: @project, members: @project_members
|
||||
= render 'projects/project_members/team', project: @project, group: group, members: @project_members
|
||||
= paginate @project_members, theme: "gitlab"
|
||||
|
|
|
@ -2,21 +2,10 @@
|
|||
= hidden_field_tag :group_id, params[:group_id]
|
||||
- if params[:project_id].present?
|
||||
= hidden_field_tag :project_id, params[:project_id]
|
||||
.dropdown.form-group.mb-lg-0.mx-lg-1{ data: { testid: "group-filter" } }
|
||||
.dropdown.form-group.mb-lg-0.mx-lg-1.gl-p-0{ data: { testid: "group-filter" } }
|
||||
%label.d-block{ for: "dashboard_search_group" }
|
||||
= _("Group")
|
||||
%button.dropdown-menu-toggle.gl-display-inline-flex.js-search-group-dropdown.gl-mt-0{ type: "button", id: "dashboard_search_group", data: { toggle: "dropdown", group_id: params[:group_id] } }
|
||||
%span.dropdown-toggle-text.gl-flex-grow-1.str-truncated-100
|
||||
= @group&.name || _("Any")
|
||||
- if @group.present?
|
||||
= link_to sprite_icon("clear"), url_for(safe_params.except(:project_id, :group_id)), class: 'search-clear js-search-clear has-tooltip', title: _('Clear')
|
||||
= icon("chevron-down")
|
||||
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
|
||||
= dropdown_title(_("Filter results by group"))
|
||||
= dropdown_filter(_("Search groups"))
|
||||
= dropdown_content
|
||||
= dropdown_loading
|
||||
|
||||
%input#js-search-group-dropdown.dropdown-menu-toggle{ value: "Loading...", data: { "initial-group-data": @group.to_json } }
|
||||
.dropdown.form-group.mb-lg-0.mx-lg-1{ data: { testid: "project-filter" } }
|
||||
%label.d-block{ for: "dashboard_search_project" }
|
||||
= _("Project")
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
- if issuable_mr > 0
|
||||
%li.issuable-mr.gl-display-none.gl-display-sm-block.has-tooltip{ title: _('Related merge requests') }
|
||||
= image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged')
|
||||
= sprite_icon('merge-request', css_class: "gl-vertical-align-middle")
|
||||
= issuable_mr
|
||||
|
||||
- if upvotes > 0
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
- show_controls = local_assigns.fetch(:show_controls, true)
|
||||
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
|
||||
- member = local_assigns.fetch(:member)
|
||||
- current_user_is_group_owner = local_assigns.fetch(:current_user_is_group_owner, false)
|
||||
- membership_source = local_assigns.fetch(:membership_source)
|
||||
- group = local_assigns.fetch(:group)
|
||||
- user = local_assigns.fetch(:user, member.user)
|
||||
- source = member.source
|
||||
- override = member.try(:override)
|
||||
|
@ -25,13 +28,13 @@
|
|||
|
||||
= render 'shared/members/its_you_badge', user: user, current_user: current_user
|
||||
|
||||
= render_if_exists 'shared/members/ee/license_badge', user: user, group: @group
|
||||
= render_if_exists 'shared/members/ee/license_badge', user: user, group: group, current_user_is_group_owner: current_user_is_group_owner
|
||||
|
||||
= render 'shared/members/blocked_badge', user: user
|
||||
|
||||
= render 'shared/members/two_factor_auth_badge', user: user
|
||||
|
||||
- if source.instance_of?(Group) && source != @group
|
||||
- if source.instance_of?(Group) && source != membership_source
|
||||
·
|
||||
= link_to source.full_name, source, class: "gl-display-inline-block inline-link"
|
||||
|
||||
|
@ -57,10 +60,9 @@
|
|||
= link_to member.created_by.name, user_path(member.created_by)
|
||||
= time_ago_with_tooltip(member.created_at)
|
||||
- if show_roles
|
||||
- current_resource = @project || @group
|
||||
.controls.member-controls.align-items-center
|
||||
= render_if_exists 'shared/members/ee/ldap_tag', can_override: member.can_override?
|
||||
- if show_controls && member.source == current_resource
|
||||
- if show_controls && member.source == membership_source
|
||||
|
||||
- if member.can_resend_invite?
|
||||
= link_to sprite_icon('paper-airplane'), polymorphic_path([:resend_invite, member]),
|
||||
|
@ -88,7 +90,7 @@
|
|||
class: ("is-active" if member.access_level == role_id),
|
||||
data: { id: role_id, el_id: dom_id(member), qa_selector: "#{role.downcase}_access_level_link" }
|
||||
= render_if_exists 'shared/members/ee/revert_ldap_group_sync_option',
|
||||
group: @group,
|
||||
group: group,
|
||||
member: member,
|
||||
can_override: member.can_override?
|
||||
.clearable-input.member-form-control{ class: [("d-sm-inline-block" unless force_mobile_view)] }
|
||||
|
@ -125,8 +127,8 @@
|
|||
= _("Delete")
|
||||
- unless force_mobile_view
|
||||
= sprite_icon('remove', css_class: 'd-none d-sm-block gl-icon')
|
||||
= render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: member.can_override?
|
||||
= render_if_exists 'shared/members/ee/override_member_buttons', group: group, member: member, user: user, action: :edit, can_override: member.can_override?
|
||||
- else
|
||||
%span.member-access-text.user-access-role= member.human_access
|
||||
|
||||
= render_if_exists 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :confirm, can_override: member.can_override?
|
||||
= render_if_exists 'shared/members/ee/override_member_buttons', group: group, member: member, user: user, action: :confirm, can_override: member.can_override?
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
- membership_source = local_assigns.fetch(:membership_source)
|
||||
- requesters = local_assigns.fetch(:requesters)
|
||||
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
|
||||
- group = local_assigns.fetch(:group)
|
||||
- current_user_is_group_owner = group && group.has_owner?(current_user)
|
||||
|
||||
- return if requesters.empty?
|
||||
|
||||
|
@ -10,4 +12,9 @@
|
|||
%strong= membership_source.name
|
||||
%span.badge.badge-pill= requesters.size
|
||||
%ul.content-list.members-list
|
||||
= render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
|
||||
= render partial: 'shared/members/member',
|
||||
collection: requesters, as: :member,
|
||||
locals: { membership_source: membership_source,
|
||||
group: group,
|
||||
force_mobile_view: force_mobile_view,
|
||||
current_user_is_group_owner: current_user_is_group_owner }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Updated list view MR icon
|
||||
merge_request: 46059
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Ensure that copy to clipboard button is visible
|
||||
merge_request: 46466
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow to create todo on GraphQL
|
||||
merge_request: 46029
|
||||
author:
|
||||
type: added
|
|
@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46209
|
|||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273405
|
||||
type: development
|
||||
group: group::testing
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: user_group_counts
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44069/
|
||||
rollout_issue_url:
|
||||
type: development
|
||||
group: group::compliance
|
||||
default_enabled: false
|
|
@ -462,6 +462,34 @@ are stored.
|
|||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
|
||||
Alternatively, if you have existing Pages deployed you can follow
|
||||
the below steps to do a no downtime transfer to a new storage location.
|
||||
|
||||
1. Pause Pages deployments by setting the following in `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
sidekiq['experimental_queue_selector'] = true
|
||||
sidekiq['queue_groups'] = [
|
||||
"feature_category!=pages"
|
||||
]
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
1. `rsync` contents from the current storage location to the new storage location: `sudo rsync -avzh --progress /var/opt/gitlab/gitlab-rails/shared/pages/ /mnt/storage/pages`
|
||||
1. Set the new storage location in `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
gitlab_rails['pages_path'] = "/mnt/storage/pages"
|
||||
```
|
||||
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
1. Verify Pages are still being served up as expected.
|
||||
1. Unpause Pages deployments by removing from `/etc/gitlab/gitlab.rb` the `sidekiq` setting set above.
|
||||
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
|
||||
1. Trigger a new Pages deployment and verify it's working as expected.
|
||||
1. Remove the old Pages storage location: `sudo rm -rf /var/opt/gitlab/gitlab-rails/shared/pages`
|
||||
1. Verify Pages are still being served up as expected.
|
||||
|
||||
## Configure listener for reverse proxy requests
|
||||
|
||||
Follow the steps below to configure the proxy listener of GitLab Pages. [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/2533) in
|
||||
|
|
|
@ -12992,6 +12992,7 @@ type Mutation {
|
|||
terraformStateDelete(input: TerraformStateDeleteInput!): TerraformStateDeletePayload
|
||||
terraformStateLock(input: TerraformStateLockInput!): TerraformStateLockPayload
|
||||
terraformStateUnlock(input: TerraformStateUnlockInput!): TerraformStateUnlockPayload
|
||||
todoCreate(input: TodoCreateInput!): TodoCreatePayload
|
||||
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
|
||||
todoRestore(input: TodoRestoreInput!): TodoRestorePayload
|
||||
todoRestoreMany(input: TodoRestoreManyInput!): TodoRestoreManyPayload
|
||||
|
@ -13276,6 +13277,11 @@ enum NamespaceProjectSort {
|
|||
Most similar to the search query
|
||||
"""
|
||||
SIMILARITY
|
||||
|
||||
"""
|
||||
Sort by storage size
|
||||
"""
|
||||
STORAGE
|
||||
}
|
||||
|
||||
input NegatedBoardIssueInput {
|
||||
|
@ -20172,6 +20178,41 @@ type TodoConnection {
|
|||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of TodoCreate
|
||||
"""
|
||||
input TodoCreateInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported
|
||||
"""
|
||||
targetId: TodoableID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of TodoCreate
|
||||
"""
|
||||
type TodoCreatePayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Errors encountered during execution of the mutation.
|
||||
"""
|
||||
errors: [String!]!
|
||||
|
||||
"""
|
||||
The to-do created
|
||||
"""
|
||||
todo: Todo
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
|
@ -20339,6 +20380,11 @@ enum TodoTargetEnum {
|
|||
MERGEREQUEST
|
||||
}
|
||||
|
||||
"""
|
||||
Identifier of Todoable
|
||||
"""
|
||||
scalar TodoableID
|
||||
|
||||
"""
|
||||
Autogenerated input type of TodosMarkAllDone
|
||||
"""
|
||||
|
@ -21541,6 +21587,11 @@ type User {
|
|||
"""
|
||||
email: String
|
||||
|
||||
"""
|
||||
Group count for the user. Available only when feature flag `user_group_counts` is enabled
|
||||
"""
|
||||
groupCount: Int
|
||||
|
||||
"""
|
||||
Group memberships of the user
|
||||
"""
|
||||
|
|
|
@ -37870,6 +37870,33 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "todoCreate",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TodoCreateInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoCreatePayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "todoMarkDone",
|
||||
"description": null,
|
||||
|
@ -39133,6 +39160,12 @@
|
|||
"description": "Most similar to the search query",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "STORAGE",
|
||||
"description": "Sort by storage size",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
|
@ -58681,6 +58714,108 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TodoCreateInput",
|
||||
"description": "Autogenerated input type of TodoCreate",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "targetId",
|
||||
"description": "The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "TodoableID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoCreatePayload",
|
||||
"description": "Autogenerated return type of TodoCreate",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Errors encountered during execution of the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "todo",
|
||||
"description": "The to-do created",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Todo",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoEdge",
|
||||
|
@ -59166,6 +59301,16 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "TodoableID",
|
||||
"description": "Identifier of Todoable",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TodosMarkAllDoneInput",
|
||||
|
@ -62400,6 +62545,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "groupCount",
|
||||
"description": "Group count for the user. Available only when feature flag `user_group_counts` is enabled",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "groupMemberships",
|
||||
"description": "Group memberships of the user",
|
||||
|
|
|
@ -2838,6 +2838,16 @@ Representing a todo entry.
|
|||
| `state` | TodoStateEnum! | State of the todo |
|
||||
| `targetType` | TodoTargetEnum! | Target type of the todo |
|
||||
|
||||
### TodoCreatePayload
|
||||
|
||||
Autogenerated return type of TodoCreate.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `todo` | Todo | The to-do created |
|
||||
|
||||
### TodoMarkDonePayload
|
||||
|
||||
Autogenerated return type of TodoMarkDone.
|
||||
|
@ -3041,6 +3051,7 @@ Autogenerated return type of UpdateSnippet.
|
|||
| ----- | ---- | ----------- |
|
||||
| `avatarUrl` | String | URL of the user's avatar |
|
||||
| `email` | String | User email |
|
||||
| `groupCount` | Int | Group count for the user. Available only when feature flag `user_group_counts` is enabled |
|
||||
| `id` | ID! | ID of the user |
|
||||
| `name` | String! | Human-readable name of the user |
|
||||
| `state` | UserState! | State of the user |
|
||||
|
@ -3770,6 +3781,7 @@ Values for sorting projects.
|
|||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| `SIMILARITY` | Most similar to the search query |
|
||||
| `STORAGE` | Sort by storage size |
|
||||
|
||||
### PackageTypeEnum
|
||||
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
type: reference, howto
|
||||
stage: Manage
|
||||
group: Access
|
||||
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
|
||||
---
|
||||
|
||||
# SCIM API **(SILVER ONLY)**
|
||||
# SCIM API (SYSTEM ONLY) **(SILVER ONLY)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9388) in [GitLab Silver](https://about.gitlab.com/pricing/) 11.10.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/9388) in [GitLab.com Silver](https://about.gitlab.com/pricing/) 11.10.
|
||||
|
||||
The SCIM API implements the [RFC7644 protocol](https://tools.ietf.org/html/rfc7644).
|
||||
The SCIM API implements the [RFC7644 protocol](https://tools.ietf.org/html/rfc7644). As this API is for
|
||||
**system** use for SCIM provider integration, it is subject to change without notice.
|
||||
|
||||
CAUTION: **Caution:**
|
||||
This API is for internal system use for connecting with a SCIM provider. While it can be used directly, it is subject to change without notice.
|
||||
|
||||
NOTE: **Note:**
|
||||
[Group SSO](../user/group/saml_sso/index.md) must be enabled for the group. For more information, see [SCIM setup documentation](../user/group/saml_sso/scim_setup.md#requirements).
|
||||
To use this API, [Group SSO](../user/group/saml_sso/index.md) must be enabled for the group.
|
||||
This API is only in use where [SCIM for Group SSO](../user/group/saml_sso/scim_setup.md) is enabled. It's a prerequisite to the creation of SCIM identities.
|
||||
|
||||
## Get a list of SAML users
|
||||
|
||||
NOTE: **Note:**
|
||||
This endpoint is used as part of the SCIM syncing mechanism and it only returns
|
||||
This endpoint is used as part of the SCIM syncing mechanism. It only returns
|
||||
a single user based on a unique ID which should match the `extern_uid` of the user.
|
||||
|
||||
```plaintext
|
||||
|
|
|
@ -282,10 +282,10 @@ When running your project pipeline at this point:
|
|||
on the related JSON object's content. The deployment job finishes whenever the deployment to EC2
|
||||
is done or has failed.
|
||||
|
||||
#### Deploy Amazon EKS
|
||||
### Deploy to Amazon EKS
|
||||
|
||||
- [How to deploy your application to a GitLab-managed Amazon EKS cluster with Auto DevOps](https://about.gitlab.com/blog/2020/05/05/deploying-application-eks/)
|
||||
|
||||
#### Deploy to Google Cloud
|
||||
## Deploy to Google Cloud
|
||||
|
||||
- [Deploying with GitLab on Google Cloud](https://about.gitlab.com/solutions/google-cloud-platform/)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
|
@ -45,6 +45,61 @@ To protect an environment:
|
|||
|
||||
The protected environment will now appear in the list of protected environments.
|
||||
|
||||
### Use the API to protect an environment
|
||||
|
||||
Alternatively, you can use the API to protect an environment:
|
||||
|
||||
1. Use a project with a CI that creates an environment. For example:
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- test
|
||||
- deploy
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script:
|
||||
- 'echo "Testing Application: ${CI_PROJECT_NAME}"'
|
||||
|
||||
production:
|
||||
stage: deploy
|
||||
when: manual
|
||||
script:
|
||||
- 'echo "Deploying to ${CI_ENVIRONMENT_NAME}"'
|
||||
environment:
|
||||
name: ${CI_JOB_NAME}
|
||||
```
|
||||
|
||||
1. Use the UI to [create a new group](../../user/group/index.md#create-a-new-group).
|
||||
For example, this group is called `protected-access-group` and has the group ID `9899826`. Note
|
||||
that the rest of the examples in these steps use this group.
|
||||
|
||||
![Group Access](img/protected_access_group_v13_6.png)
|
||||
|
||||
1. Use the API to add a user to the group as a reporter:
|
||||
|
||||
```shell
|
||||
$ curl --request POST --header "PRIVATE-TOKEN: xxxxxxxxxxxx" --data "user_id=3222377&access_level=20" "https://gitlab.com/api/v4/groups/9899826/members"
|
||||
|
||||
{"id":3222377,"name":"Sean Carroll","username":"sfcarroll","state":"active","avatar_url":"https://assets.gitlab-static.net/uploads/-/system/user/avatar/3222377/avatar.png","web_url":"https://gitlab.com/sfcarroll","access_level":20,"created_at":"2020-10-26T17:37:50.309Z","expires_at":null}
|
||||
```
|
||||
|
||||
1. Use the API to add the group to the project as a reporter:
|
||||
|
||||
```shell
|
||||
$ curl --request POST --header "PRIVATE-TOKEN: xxxxxxxxxxxx" --request POST "https://gitlab.com/api/v4/projects/22034114/share?group_id=9899826&group_access=20"
|
||||
|
||||
{"id":1233335,"project_id":22034114,"group_id":9899826,"group_access":20,"expires_at":null}
|
||||
```
|
||||
|
||||
1. Use the API to add the group with protected environment access:
|
||||
|
||||
```shell
|
||||
curl --header 'Content-Type: application/json' --request POST --data '{"name": "production", "deploy_access_levels": [{"group_id": 9899826}]}' --header "PRIVATE-TOKEN: xxxxxxxxxxx" "https://gitlab.com/api/v4/projects/22034114/protected_environments"
|
||||
```
|
||||
|
||||
The group now has access and can be seen in the UI.
|
||||
|
||||
## Environment access by group membership
|
||||
|
||||
A user may be granted access to protected environments as part of
|
||||
|
|
|
@ -128,6 +128,12 @@ This helps you avoid having to add the `only:` rule to all of your jobs to make
|
|||
them always run. You can use this format to set up a Review App, helping to
|
||||
save resources.
|
||||
|
||||
### Using SAST, DAST, and other Secure Templates with Pipelines for Merge Requests
|
||||
|
||||
To use [Secure templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/Security)
|
||||
with pipelines for merge requests, you may need to apply a `rules: if: merge_request_event` for the
|
||||
Secure scans to run in the same pipeline as the commit.
|
||||
|
||||
#### Excluding certain branches
|
||||
|
||||
Pipelines for merge requests require special treatment when
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Adding a new Service Component to GitLab
|
||||
|
||||
The GitLab product is made up of several service components that run as independent system processes in communication with each other. These services can be run on the same instance, or spread across different instances. A list of the existing components can be found in the [GitLab architecture overview](architecture.md).
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# API style guide
|
||||
|
||||
This style guide recommends best practices for API development.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Application limits development
|
||||
|
||||
This document provides a development guide for contributors to add application
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Application secrets
|
||||
|
||||
This page is a development guide for application secrets.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Approval Rules **(STARTER)**
|
||||
|
||||
This document explains the backend design and flow of all related functionality
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# GitLab architecture overview
|
||||
|
||||
## Software delivery
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Generating chaos in a test GitLab instance
|
||||
|
||||
As [Werner Vogels](https://twitter.com/Werner), the CTO at Amazon Web Services, famously put it, **Everything fails, all the time**.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Code comments
|
||||
|
||||
Whenever you add comment to the code that is expected to be addressed at any time
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
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
|
||||
---
|
||||
|
||||
# Code Intelligence
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/1576) in GitLab 13.1.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Creating enums
|
||||
|
||||
When creating a new enum, it should use the database type `SMALLINT`.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Danger bot
|
||||
|
||||
The GitLab CI/CD pipeline includes a `danger-review` job that uses [Danger](https://github.com/danger/danger)
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Setting Multiple Values
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32921) in GitLab 13.5.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Delete existing migrations
|
||||
|
||||
When removing existing migrations from the GitLab project, you have to take into account
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Deprecation guidelines
|
||||
|
||||
This page includes information about how and when to remove or make breaking
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
description: "Learn how GitLab docs' global navigation works and how to add new items."
|
||||
---
|
||||
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# GitLab Docs monthly release process
|
||||
|
||||
When a new GitLab version is released on the 22nd, we need to create the respective
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Guidelines for implementing Enterprise Edition features
|
||||
|
||||
- **Write the code and the tests.**: As with any code, EE features should have
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Dealing with email in development
|
||||
|
||||
## Ensuring compatibility with mailer Sidekiq jobs
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Experiment Guide
|
||||
|
||||
Experiments can be conducted by any GitLab team, most often the teams from the [Growth Sub-department](https://about.gitlab.com/handbook/engineering/development/growth/). Experiments are not tied to releases because they will primarily target GitLab.com.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Accessibility & Readability
|
||||
|
||||
## Resources
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
When you are developing a new feature that requires architectural design, or if
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Axios
|
||||
|
||||
We use [Axios](https://github.com/axios/axios) to communicate with the server in Vue applications and most new code.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Frontend dependencies
|
||||
|
||||
## Package manager
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Design Patterns
|
||||
|
||||
## Singletons
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Frontend Development Process
|
||||
|
||||
You can find more about the organization of the frontend team in the [handbook](https://about.gitlab.com/handbook/engineering/frontend/).
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# DropLab
|
||||
|
||||
A generic dropdown for all of your custom dropdown needs.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Ajax
|
||||
|
||||
`Ajax` is a droplab plugin that allows for retrieving and rendering list data from a server.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Filter
|
||||
|
||||
`Filter` is a plugin that allows for filtering data that has been added
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# InputSetter
|
||||
|
||||
`InputSetter` is a plugin that allows for updating DOM out of the scope of droplab when a list item is clicked.
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
# Editor Lite
|
||||
|
||||
## Background
|
||||
|
||||
**Editor Lite** is a technological product driving [Web Editor](../../user/project/repository/web_editor.md), [Snippets](../../user/snippets.md), [CI Linter](../../ci/lint.md), etc. Editor Lite is the driving technology for any single-file editing experience across the product.
|
||||
|
||||
Editor Lite is a thin wrapper around [the Monaco editor](https://microsoft.github.io/monaco-editor/index.html) that provides the necessary helpers and abstractions and extends Monaco using extensions.
|
||||
|
||||
## How to use Editor Lite
|
||||
|
||||
Editor Lite is framework-agnostic and can be used in any application, whether it's Rails or Vue. For the convenience of integration, we have [the dedicated `<editor-lite>` Vue component](#vue-component), but in general, the integration of Editor Lite is pretty straightforward:
|
||||
|
||||
1. Import Editor Lite:
|
||||
|
||||
```javascript
|
||||
import EditorLite from '~/editor/editor_lite';
|
||||
```
|
||||
|
||||
1. Initialize global editor for the view:
|
||||
|
||||
```javascript
|
||||
const editor = new EditorLite({
|
||||
// Editor Options.
|
||||
// The list of all accepted options can be found at
|
||||
// https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html
|
||||
});
|
||||
```
|
||||
|
||||
1. Create an editor's instance:
|
||||
|
||||
```javascript
|
||||
editor.createInstance({
|
||||
// Editor Lite configuration options.
|
||||
})
|
||||
```
|
||||
|
||||
An instance of Editor Lite accepts the following configuration options:
|
||||
|
||||
| Option | Required? | Description |
|
||||
| ---- | ---- | ---- |
|
||||
| `el` | `true` | `HTML Node`: element on which to render the editor |
|
||||
| `blobPath` | `false` | `String`: the name of a file to render in the editor. It is used to identify the correct syntax highlighter to use with that or another file type. Can accept wildcard as in `*.js` when the actual filename isn't known or doesn't play any role |
|
||||
| `blobContent` | `false` | `String`: the initial content to be rendered in the editor |
|
||||
| `extensions` | `false` | `Array`: extensions to use in this instance |
|
||||
| `blobGlobalId` | `false` | `String`: auto-generated property.</br>**Note:** this prop might go away in the future. Do not pass `blobGlobalId` unless you know what you're doing.|
|
||||
| [Editor Options](https://microsoft.github.io/monaco-editor/api/enums/monaco.editor.editoroption.html) | `false` | `Object(s)`: any prop outside of the list above is treated as an Editor Option for this particular instance. This way, one can override global Editor Options on the instance level. |
|
||||
|
||||
## API
|
||||
|
||||
The editor follows the same public API as [provided by Monaco editor](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) with just a few additional functions on the instance level:
|
||||
|
||||
| Function | Arguments | Description
|
||||
| ----- | ----- | ----- |
|
||||
| `updateModelLanguage` | `path`: String | Updates the instance's syntax highlighting to follow the extension of the passed `path`. Available only on _instance_ level|
|
||||
| `use` | Array of objects | Array of **extensions** to apply to the instance. Note: `use` accepts only the array of _objects_, which means that the extensions' ES6 modules should be fetched and resolved in your views/components before being passed to `use`. This prop is available on _instance_ (applies extension to this particular instance) and _global edtor_ (applies the same extension to all instances) levels. |
|
||||
| Monaco Editor options | See [documentation](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) | Default Monaco editor options |
|
||||
|
||||
## Tips
|
||||
|
||||
1. Editor's loading state.
|
||||
|
||||
Editor Lite comes with the loading state built-in, making spinners and loaders rarely needed in HTML. To benefit the built-in loading state, set the `data-editor-loading` property on the HTML element that is supposed to contain the editor. Editor Lite will show the loader automatically while it's bootstrapping.
|
||||
![Editor Lite: loading state](img/editor_lite_loading.png)
|
||||
|
||||
1. Update syntax highlighting if the file name changes.
|
||||
|
||||
```javascript
|
||||
// fileNameEl here is the HTML input element that contains the file name
|
||||
fileNameEl.addEventListener('change', () => {
|
||||
this.editor.updateModelLanguage(fileNameEl.value);
|
||||
});
|
||||
```
|
||||
|
||||
1. Get the editor's content.
|
||||
|
||||
We might set up listeners on the editor for every change but it rapidly can become an expensive operation. Instead , we can get editor's content when it's needed. For example on a form's submission:
|
||||
|
||||
```javascript
|
||||
form.addEventListener('submit', () => {
|
||||
my_content_variable = this.editor.getValue();
|
||||
});
|
||||
```
|
||||
|
||||
1. Performance
|
||||
|
||||
Even though Editor Lite itself is extremely slim, it still depends on Monaco editor. Monaco is not an easily tree-shakeable module. Hence, every time you add Editor Lite to a view, the JavaScript bundle's size significantly increases, affecting your view's loading performance. To avoid that, it is recommended to import the editor on demand on those views where it is not 100% certain that the editor will be used. Or if the editor is a secondary element of the view. Loading Editor Lite on demand is no different from loading any other module:
|
||||
|
||||
```javascript
|
||||
someActionFunction() {
|
||||
import(/* webpackChunkName: 'EditorLite' */ '~/editor/editor_lite').
|
||||
then(({ default: EditorLite }) => {
|
||||
const editor = new EditorLite();
|
||||
...
|
||||
});
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Extensions
|
||||
|
||||
Editor Lite has been built to provide a universal, extensible editing tool to the whole product, which would not depend on any particular group. Even though the Editor Lite's core is owned by [Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor-fe/), the main functional elements — extensions — can be owned by any group. Editor Lite extensions' main idea is that the core of the editor remains very slim and stable. At the same time, whatever new functionality is needed can be added as an extension to this core, without touching the core itself. It allows any group to build and own any new editing functionality without being afraid of it being broken or overridden with the Editor Lite changes.
|
||||
|
||||
Structurally, the complete implementation of Editor Lite could be presented as the following diagram:
|
||||
|
||||
```mermaid
|
||||
graph TD;
|
||||
B[Extension 1]---A[Editor Lite]
|
||||
C[Extension 2]---A[Editor Lite]
|
||||
D[Extension 3]---A[Editor Lite]
|
||||
E[...]---A[Editor Lite]
|
||||
F[Extension N]---A[Editor Lite]
|
||||
A[Editor Lite]---Z[Monaco]
|
||||
```
|
||||
|
||||
Technically, an extension is just an ES6 module that exports a JavaScript object:
|
||||
|
||||
```javascript
|
||||
import { Position } from 'monaco-editor';
|
||||
|
||||
export default {
|
||||
navigateFileStart() {
|
||||
this.setPosition(new Position(1, 1));
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
Important things to note here:
|
||||
|
||||
- We can depend on other modules in our extensions. This organization helps keep the size of Editor Lite's core at bay by importing dependencies only when needed.
|
||||
- `this` in extension's functions refers to the current Editor Lite instance. Using `this`, you get access to the complete instance's API, such as the `setPosition()` method in this particular case.
|
||||
|
||||
### Using an existing extension
|
||||
|
||||
Adding an extension to Editor Lite's instance is simple:
|
||||
|
||||
```javascript
|
||||
import EditorLite from '~/editor/editor_lite';
|
||||
import MyExtension from '~/my_extension';
|
||||
|
||||
const editor = new EditorLite().createInstance({
|
||||
...
|
||||
});
|
||||
editor.use(MyExtension);
|
||||
```
|
||||
|
||||
### Creating an extension
|
||||
|
||||
Let's create our first Editor Lite extension. As aforementioned, extensions are ES6 modules exporting the simple `Object` that is used to extend Editor Lite's functionality. As the most straightforward test, let's create an extension that extends Editor Lite with a new function that, when called, will output editor's content in `alert`.
|
||||
|
||||
`~/my_folder/my_fancy_extension.js:`
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
throwContentAtMe() {
|
||||
alert(this.getValue());
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
And that's it with our extension! Note that we're using `this` as a reference to the instance. And through it, we get access to the complete underlying [Monaco editor API](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html) like `getValue()` in this case.
|
||||
|
||||
Now let's use our extension:
|
||||
|
||||
`~/my_folder/component_bundle.js`:
|
||||
|
||||
```javascript
|
||||
import EditorLite from '~/editor/editor_lite';
|
||||
import MyFancyExtension from './my_fancy_extension';
|
||||
|
||||
const editor = new EditorLite().createInstance({
|
||||
...
|
||||
});
|
||||
editor.use(MyFancyExtension);
|
||||
...
|
||||
someButton.addEventListener('click', () => {
|
||||
editor.throwContentAtMe();
|
||||
});
|
||||
```
|
||||
|
||||
First of all, we import Editor Lite and our new extension. Then we create the editor and its instance. By default Editor Lite has no `throwContentAtMe` method. But the `editor.use(MyFancyExtension)` line brings that method to our instance. After that, we can use it any time we need it. In this case, we call it when some theoretical button has been clicked.
|
||||
|
||||
This script would result in an alert containing the editor's content when `someButton` is clicked.
|
||||
|
||||
![Editor Lite new extension's result](img/editor_lite_create_ext.png)
|
||||
|
||||
### Tips
|
||||
|
||||
1. Performance
|
||||
|
||||
Just like Editor Lite itself, any extension can be loaded on demand to not harm loading performance of the views:
|
||||
|
||||
```javascript
|
||||
const EditorPromise = import(
|
||||
/* webpackChunkName: 'EditorLite' */ '~/editor/editor_lite'
|
||||
);
|
||||
const MarkdownExtensionPromise = import('~/editor/editor_markdown_ext');
|
||||
|
||||
Promise.all([EditorPromise, MarkdownExtensionPromise])
|
||||
.then(([{ default: EditorLite }, { default: MarkdownExtension }]) => {
|
||||
const editor = new EditorLite().createInstance({
|
||||
...
|
||||
});
|
||||
editor.use(MarkdownExtension);
|
||||
});
|
||||
```
|
||||
|
||||
1. Using multiple extensions
|
||||
|
||||
Just pass the array of extensions to your `use` method:
|
||||
|
||||
```javascript
|
||||
editor.use([FileTemplateExtension, MyFancyExtension]);
|
||||
```
|
||||
|
||||
## <a id="vue-component"></a>`<editor-lite>` Vue component
|
||||
|
||||
TBD
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Emojis
|
||||
|
||||
GitLab supports native Unicode emojis and falls back to image-based emojis selectively
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Frontend FAQ
|
||||
|
||||
## Rules of Frontend FAQ
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Icons and SVG Illustrations
|
||||
|
||||
We manage our own icon and illustration library in the [`gitlab-svgs`](https://gitlab.com/gitlab-org/gitlab-svgs)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 788 KiB |
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Frontend Development Guidelines
|
||||
|
||||
This document describes various guidelines to ensure consistency and quality
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Implementing keyboard shortcuts
|
||||
|
||||
We use [Mousetrap](https://craig.is/killing/mice) to implement keyboard
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Performance
|
||||
|
||||
## Best Practices
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Principles
|
||||
|
||||
These principles will ensure that your frontend contribution starts off in the right direction.
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# Security
|
||||
|
||||
## Resources
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
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
|
||||
---
|
||||
|
||||
# HTML style guide
|
||||
|
||||
## Buttons
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue