Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6dd9e3644e
commit
3a0f6ebaa9
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
|
||||
|
@ -21,7 +22,6 @@ export default {
|
|||
return {
|
||||
contacts: [],
|
||||
error: false,
|
||||
errorMessages: [],
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
@ -49,6 +49,9 @@ export default {
|
|||
showNewForm() {
|
||||
return this.$route.path.startsWith('/new');
|
||||
},
|
||||
canCreateNew() {
|
||||
return parseBoolean(this.canAdminCrmContact);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
extractContacts(data) {
|
||||
|
@ -60,17 +63,11 @@ export default {
|
|||
|
||||
this.$router.push({ path: '/new' });
|
||||
},
|
||||
hideNewForm() {
|
||||
hideNewForm(success) {
|
||||
if (success) this.$toast.show(s__('Crm|Contact has been added'));
|
||||
|
||||
this.$router.replace({ path: '/' });
|
||||
},
|
||||
handleError(errors) {
|
||||
this.error = true;
|
||||
if (errors) this.errorMessages = errors;
|
||||
},
|
||||
dismissError() {
|
||||
this.error = false;
|
||||
this.errorMessages = [];
|
||||
},
|
||||
getIssuesPath(path, value) {
|
||||
return `${path}?scope=all&state=opened&crm_contact_id=${value}`;
|
||||
},
|
||||
|
@ -108,9 +105,8 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="dismissError">
|
||||
<div v-if="errorMessages.length == 0">{{ $options.i18n.errorText }}</div>
|
||||
<div v-for="(message, index) in errorMessages" :key="index">{{ message }}</div>
|
||||
<gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="error = false">
|
||||
{{ $options.i18n.errorText }}
|
||||
</gl-alert>
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-baseline gl-flex-direction-row gl-justify-content-space-between gl-mt-6"
|
||||
|
@ -120,7 +116,7 @@ export default {
|
|||
</h2>
|
||||
<div class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end">
|
||||
<gl-button
|
||||
v-if="canAdminCrmContact"
|
||||
v-if="canCreateNew"
|
||||
variant="confirm"
|
||||
data-testid="new-contact-button"
|
||||
@click="displayNewForm"
|
||||
|
@ -129,7 +125,7 @@ export default {
|
|||
</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
<new-contact-form v-if="showNewForm" @close="hideNewForm" @error="handleError" />
|
||||
<new-contact-form v-if="showNewForm" :drawer-open="showNewForm" @close="hideNewForm" />
|
||||
<gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" />
|
||||
<gl-table
|
||||
v-else
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
|
||||
import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput } from '@gitlab/ui';
|
||||
import { produce } from 'immer';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
|
@ -9,11 +9,19 @@ import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlDrawer,
|
||||
GlFormGroup,
|
||||
GlFormInput,
|
||||
},
|
||||
inject: ['groupFullPath', 'groupId'],
|
||||
props: {
|
||||
drawerOpen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
firstName: '',
|
||||
|
@ -22,6 +30,7 @@ export default {
|
|||
email: '',
|
||||
description: '',
|
||||
submitting: false,
|
||||
errorMessages: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -48,24 +57,21 @@ export default {
|
|||
update: this.updateCache,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.customerRelationsContactCreate.errors.length === 0) this.close();
|
||||
if (data.customerRelationsContactCreate.errors.length === 0) this.close(true);
|
||||
|
||||
this.submitting = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.error();
|
||||
this.errorMessages = [__('Something went wrong. Please try again.')];
|
||||
this.submitting = false;
|
||||
});
|
||||
},
|
||||
close() {
|
||||
this.$emit('close');
|
||||
},
|
||||
error(errors = null) {
|
||||
this.$emit('error', errors);
|
||||
close(success) {
|
||||
this.$emit('close', success);
|
||||
},
|
||||
updateCache(store, { data: { customerRelationsContactCreate } }) {
|
||||
if (customerRelationsContactCreate.errors.length > 0) {
|
||||
this.error(customerRelationsContactCreate.errors);
|
||||
this.errorMessages = customerRelationsContactCreate.errors;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -90,6 +96,15 @@ export default {
|
|||
data,
|
||||
});
|
||||
},
|
||||
getDrawerHeaderHeight() {
|
||||
const wrapperEl = document.querySelector('.content-wrapper');
|
||||
|
||||
if (wrapperEl) {
|
||||
return `${wrapperEl.offsetTop}px`;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
buttonLabel: s__('Crm|Create new contact'),
|
||||
|
@ -99,12 +114,28 @@ export default {
|
|||
email: s__('Crm|Email'),
|
||||
phone: s__('Crm|Phone number (optional)'),
|
||||
description: s__('Crm|Description (optional)'),
|
||||
title: s__('Crm|New Contact'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-md-4">
|
||||
<gl-drawer
|
||||
class="gl-drawer-responsive"
|
||||
:open="drawerOpen"
|
||||
:header-height="getDrawerHeaderHeight()"
|
||||
@close="close(false)"
|
||||
>
|
||||
<template #title>
|
||||
<h4>{{ $options.i18n.title }}</h4>
|
||||
</template>
|
||||
<gl-alert v-if="errorMessages.length" variant="danger" @dismiss="errorMessages = []">
|
||||
<ul class="gl-mb-0! gl-ml-5">
|
||||
<li v-for="error in errorMessages" :key="error">
|
||||
{{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
</gl-alert>
|
||||
<form @submit.prevent="save">
|
||||
<gl-form-group :label="$options.i18n.firstName" label-for="contact-first-name">
|
||||
<gl-form-input id="contact-first-name" v-model="firstName" />
|
||||
|
@ -121,7 +152,10 @@ export default {
|
|||
<gl-form-group :label="$options.i18n.description" label-for="contact-description">
|
||||
<gl-form-input id="contact-description" v-model="description" />
|
||||
</gl-form-group>
|
||||
<div class="form-actions">
|
||||
<span class="gl-float-right">
|
||||
<gl-button data-testid="cancel-button" @click="close(false)">
|
||||
{{ $options.i18n.cancel }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
variant="confirm"
|
||||
:disabled="invalid"
|
||||
|
@ -130,11 +164,7 @@ export default {
|
|||
type="submit"
|
||||
>{{ $options.i18n.buttonLabel }}</gl-button
|
||||
>
|
||||
<gl-button data-testid="cancel-button" @click="close">
|
||||
{{ $options.i18n.cancel }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</span>
|
||||
</form>
|
||||
<div class="gl-pb-5"></div>
|
||||
</div>
|
||||
</gl-drawer>
|
||||
</template>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GlToast } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import VueRouter from 'vue-router';
|
||||
|
@ -6,6 +7,7 @@ import CrmContactsRoot from './components/contacts_root.vue';
|
|||
|
||||
Vue.use(VueApollo);
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(GlToast);
|
||||
|
||||
export default () => {
|
||||
const el = document.getElementById('js-crm-contacts-app');
|
||||
|
|
|
@ -4,7 +4,7 @@ import $ from 'jquery';
|
|||
import { property } from 'lodash';
|
||||
|
||||
import issueableEventHub from '~/issues_list/eventhub';
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import LabelsSelect from '~/labels/labels_select';
|
||||
import MilestoneSelect from '~/milestones/milestone_select';
|
||||
import initIssueStatusSelect from './init_issue_status_select';
|
||||
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable no-new */
|
||||
|
||||
import { getSidebarOptions } from '~/sidebar/mount_sidebar';
|
||||
import IssuableContext from './issuable_context';
|
||||
import Sidebar from './right_sidebar';
|
||||
import IssuableContext from '~/issuable/issuable_context';
|
||||
import Sidebar from '~/right_sidebar';
|
||||
|
||||
export default () => {
|
||||
const sidebarOptEl = document.querySelector('.js-sidebar-options');
|
|
@ -1,8 +1,8 @@
|
|||
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
||||
import $ from 'jquery';
|
||||
import Cookies from 'js-cookie';
|
||||
import { loadCSSFile } from './lib/utils/css_utils';
|
||||
import UsersSelect from './users_select';
|
||||
import { loadCSSFile } from '~/lib/utils/css_utils';
|
||||
import UsersSelect from '~/users_select';
|
||||
|
||||
export default class IssuableContext {
|
||||
constructor(currentUser) {
|
|
@ -1,14 +1,14 @@
|
|||
import $ from 'jquery';
|
||||
import Pikaday from 'pikaday';
|
||||
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
|
||||
import Autosave from './autosave';
|
||||
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
|
||||
import { loadCSSFile } from './lib/utils/css_utils';
|
||||
import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
|
||||
import { select2AxiosTransport } from './lib/utils/select2_utils';
|
||||
import { queryToObject, objectToQuery } from './lib/utils/url_utility';
|
||||
import UsersSelect from './users_select';
|
||||
import ZenMode from './zen_mode';
|
||||
import Autosave from '~/autosave';
|
||||
import AutoWidthDropdownSelect from '~/issuable/auto_width_dropdown_select';
|
||||
import { loadCSSFile } from '~/lib/utils/css_utils';
|
||||
import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility';
|
||||
import { select2AxiosTransport } from '~/lib/utils/select2_utils';
|
||||
import { queryToObject, objectToQuery } from '~/lib/utils/url_utility';
|
||||
import UsersSelect from '~/users_select';
|
||||
import ZenMode from '~/zen_mode';
|
||||
|
||||
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
|
||||
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
|
|
@ -1,4 +1,4 @@
|
|||
import issuableInitBulkUpdateSidebar from '~/issuable_bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
|
||||
export default class IssuableIndex {
|
||||
constructor(pagePrefix = 'issuable_') {
|
|
@ -3,7 +3,7 @@ import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/u
|
|||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import createFlash, { FLASH_TYPES } from '~/flash';
|
||||
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
|
||||
import { IssuableType } from '~/issuable_show/constants';
|
||||
import { IssuableType } from '~/vue_shared/issuable/show/constants';
|
||||
import { IssuableStatus, IssueStateEvent } from '~/issues/show/constants';
|
||||
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
|
|
|
@ -499,7 +499,7 @@ export default {
|
|||
async handleBulkUpdateClick() {
|
||||
if (!this.hasInitBulkEdit) {
|
||||
const initBulkUpdateSidebar = await import(
|
||||
'~/issuable_bulk_update_sidebar/issuable_init_bulk_update_sidebar'
|
||||
'~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar'
|
||||
);
|
||||
initBulkUpdateSidebar.default.init('issuable_');
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable func-names */
|
||||
|
||||
import $ from 'jquery';
|
||||
import Api from './api';
|
||||
import { humanize } from './lib/utils/text_utility';
|
||||
import Api from '~/api';
|
||||
import { humanize } from '~/lib/utils/text_utility';
|
||||
|
||||
export default class CreateLabelDropdown {
|
||||
constructor($el, namespacePath, projectPath) {
|
|
@ -1,8 +1,8 @@
|
|||
import $ from 'jquery';
|
||||
import { __ } from '~/locale';
|
||||
import { fixTitle, hide } from '~/tooltips';
|
||||
import createFlash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
const tooltipTitles = {
|
||||
group: __('Unsubscribe at group level'),
|
|
@ -3,9 +3,9 @@
|
|||
import $ from 'jquery';
|
||||
import Sortable from 'sortablejs';
|
||||
import { dispose } from '~/tooltips';
|
||||
import createFlash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { __ } from './locale';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default class LabelManager {
|
||||
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
|
|
@ -4,12 +4,12 @@
|
|||
import $ from 'jquery';
|
||||
import { difference, isEqual, escape, sortBy, template, union } from 'lodash';
|
||||
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
|
||||
import IssuableBulkUpdateActions from '~/issuable_bulk_update_sidebar/issuable_bulk_update_actions';
|
||||
import IssuableBulkUpdateActions from '~/issuable/bulk_update_sidebar/issuable_bulk_update_actions';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import CreateLabelDropdown from './create_label';
|
||||
import createFlash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { sprintf, __ } from './locale';
|
||||
|
||||
export default class LabelsSelect {
|
||||
constructor(els, options = {}) {
|
|
@ -1,8 +1,8 @@
|
|||
import $ from 'jquery';
|
||||
import { fixTitle } from '~/tooltips';
|
||||
import createFlash from './flash';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { __ } from './locale';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
const tooltipTitles = {
|
||||
group: {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import initIssuableSidebar from '../init_issuable_sidebar';
|
||||
import initIssuableSidebar from '~/issuable/init_issuable_sidebar';
|
||||
import MergeConflictsResolverApp from './merge_conflict_resolver_app.vue';
|
||||
import { createStore } from './store';
|
||||
|
||||
|
|
|
@ -86,8 +86,8 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div class="gl-display-flex gl-justify-content-space-between gl-mb-3">
|
||||
<gl-form-checkbox v-if="!hiddenDelete" v-model="selectAll" class="gl-ml-2">
|
||||
<div class="gl-display-flex gl-justify-content-space-between gl-mb-3 gl-align-items-center">
|
||||
<gl-form-checkbox v-if="!hiddenDelete" v-model="selectAll" class="gl-ml-2 gl-pt-2">
|
||||
<span class="gl-font-weight-bold">{{ title }}</span>
|
||||
</gl-form-checkbox>
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import Labels from '../../../../labels';
|
||||
import Labels from '~/labels/labels';
|
||||
|
||||
new Labels(); // eslint-disable-line no-new
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import Labels from '../../../../labels';
|
||||
import Labels from '~/labels/labels';
|
||||
|
||||
new Labels(); // eslint-disable-line no-new
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
|
||||
import issuableInitBulkUpdateSidebar from '~/issuable_bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
import { mountIssuablesListApp, mountIssuesListApp } from '~/issues_list';
|
||||
import initManualOrdering from '~/manual_ordering';
|
||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Labels from 'ee_else_ce/labels';
|
||||
import Labels from 'ee_else_ce/labels/labels';
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Labels();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import initDeleteLabelModal from '~/delete_label_modal';
|
||||
import initLabels from '~/init_labels';
|
||||
import initDeleteLabelModal from '~/labels/delete_label_modal';
|
||||
import initLabels from '~/labels/init_labels';
|
||||
|
||||
initLabels();
|
||||
initDeleteLabelModal();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Labels from 'ee_else_ce/labels';
|
||||
import Labels from 'ee_else_ce/labels/labels';
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Labels();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
|
||||
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
|
||||
import issuableInitBulkUpdateSidebar from '~/issuable_bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||
import projectSelect from '~/project_select';
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/* eslint-disable no-new */
|
||||
|
||||
import $ from 'jquery';
|
||||
import IssuableForm from 'ee_else_ce/issuable_form';
|
||||
import IssuableForm from 'ee_else_ce/issuable/issuable_form';
|
||||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||
import GLForm from '~/gl_form';
|
||||
import initSuggestions from '~/issues/suggestions';
|
||||
import initIssuableTypeSelector from '~/issues/type_selector';
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import LabelsSelect from '~/labels/labels_select';
|
||||
import MilestoneSelect from '~/milestones/milestone_select';
|
||||
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable
|
|||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||
import initCsvImportExportButtons from '~/issuable/init_csv_import_export_buttons';
|
||||
import initIssuableByEmail from '~/issuable/init_issuable_by_email';
|
||||
import IssuableIndex from '~/issuable_index';
|
||||
import IssuableIndex from '~/issuable/issuable_index';
|
||||
import { mountIssuablesListApp, mountIssuesListApp, mountJiraIssuesListApp } from '~/issues_list';
|
||||
import initManualOrdering from '~/manual_ordering';
|
||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import loadAwardsHandler from '~/awards_handler';
|
||||
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
|
||||
import initIssuableSidebar from '~/init_issuable_sidebar';
|
||||
import { IssuableType } from '~/issuable_show/constants';
|
||||
import initIssuableSidebar from '~/issuable/init_issuable_sidebar';
|
||||
import { IssuableType } from '~/vue_shared/issuable/show/constants';
|
||||
import Issue from '~/issue';
|
||||
import { initIncidentApp, initIncidentHeaderActions } from '~/issues/show/incident';
|
||||
import { initIssuableApp, initIssueHeaderActions } from '~/issues/show/issue';
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import Labels from 'ee_else_ce/labels';
|
||||
import Labels from 'ee_else_ce/labels/labels';
|
||||
|
||||
new Labels(); // eslint-disable-line no-new
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import initDeleteLabelModal from '~/delete_label_modal';
|
||||
import initLabels from '~/init_labels';
|
||||
import initDeleteLabelModal from '~/labels/delete_label_modal';
|
||||
import initLabels from '~/labels/init_labels';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import PromoteLabelModal from '../components/promote_label_modal.vue';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Labels from 'ee_else_ce/labels';
|
||||
import Labels from 'ee_else_ce/labels/labels';
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Labels();
|
||||
|
|
|
@ -3,7 +3,7 @@ import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
|||
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
|
||||
import initCsvImportExportButtons from '~/issuable/init_csv_import_export_buttons';
|
||||
import initIssuableByEmail from '~/issuable/init_issuable_by_email';
|
||||
import IssuableIndex from '~/issuable_index';
|
||||
import IssuableIndex from '~/issuable/issuable_index';
|
||||
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
|
||||
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/* eslint-disable no-new */
|
||||
|
||||
import $ from 'jquery';
|
||||
import IssuableForm from 'ee_else_ce/issuable_form';
|
||||
import IssuableForm from 'ee_else_ce/issuable/issuable_form';
|
||||
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
|
||||
import Diff from '~/diff';
|
||||
import GLForm from '~/gl_form';
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import LabelsSelect from '~/labels/labels_select';
|
||||
import MilestoneSelect from '~/milestones/milestone_select';
|
||||
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
|
|||
import loadAwardsHandler from '~/awards_handler';
|
||||
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
|
||||
import { initPipelineCountListener } from '~/commit/pipelines/utils';
|
||||
import initIssuableSidebar from '~/init_issuable_sidebar';
|
||||
import initIssuableSidebar from '~/issuable/init_issuable_sidebar';
|
||||
import StatusBox from '~/issuable/components/status_box.vue';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import initSourcegraph from '~/sourcegraph';
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
<div class="gl-display-flex gl-align-items-center gl-py-3">
|
||||
<div
|
||||
v-if="$slots['left-action']"
|
||||
class="gl-w-7 gl-display-none gl-sm-display-flex gl-justify-content-start gl-pl-2"
|
||||
class="gl-w-7 gl-display-flex gl-justify-content-start gl-pl-2"
|
||||
>
|
||||
<slot name="left-action"></slot>
|
||||
</div>
|
||||
|
@ -105,7 +105,7 @@ export default {
|
|||
</div>
|
||||
<div
|
||||
v-if="$slots['right-action']"
|
||||
class="gl-w-9 gl-display-none gl-sm-display-flex gl-justify-content-end gl-pr-1"
|
||||
class="gl-w-9 gl-display-flex gl-justify-content-end gl-pr-1"
|
||||
>
|
||||
<slot name="right-action"></slot>
|
||||
</div>
|
||||
|
|
|
@ -174,3 +174,30 @@ body {
|
|||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-drawer-responsive {
|
||||
// Both width & min-width
|
||||
// are defined as per Pajamas
|
||||
// See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44902#note_429056182
|
||||
width: 28%;
|
||||
min-width: 400px;
|
||||
padding-left: $gl-padding;
|
||||
padding-right: $gl-padding;
|
||||
box-shadow: none;
|
||||
background-color: $gray-10;
|
||||
border-left: 1px solid $gray-100;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
min-width: unset;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// These overrides should not happen here,
|
||||
// we should ideally have support for custom
|
||||
// header and body classes in `GlDrawer`.
|
||||
.gl-drawer-header,
|
||||
.gl-drawer-body > * {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ module TimeZoneHelper
|
|||
end
|
||||
end
|
||||
|
||||
def local_time_instance(timezone)
|
||||
def local_timezone_instance(timezone)
|
||||
return Time.zone if timezone.blank?
|
||||
|
||||
ActiveSupport::TimeZone.new(timezone) || Time.zone
|
||||
|
@ -42,7 +42,7 @@ module TimeZoneHelper
|
|||
def local_time(timezone)
|
||||
return if timezone.blank?
|
||||
|
||||
time_zone_instance = local_time_instance(timezone)
|
||||
time_zone_instance = local_timezone_instance(timezone)
|
||||
time_zone_instance.now.strftime("%-l:%M %p")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class NamespacePolicy < ::Namespaces::UserNamespacePolicy
|
||||
class NamespacePolicy < BasePolicy
|
||||
# NamespacePolicy has been traditionally for user namespaces.
|
||||
# So these policies have been moved into Namespaces::UserNamespacePolicy.
|
||||
# Once the user namespace conversion is complete, we can look at
|
||||
# either removing this file or locating common namespace policy items
|
||||
# here.
|
||||
# See https://gitlab.com/groups/gitlab-org/-/epics/6689 for details
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Namespaces
|
||||
class ProjectNamespacePolicy < BasePolicy
|
||||
class ProjectNamespacePolicy < NamespacePolicy
|
||||
# For now users are not granted any permissions on project namespace
|
||||
# as it's completely hidden to them. When we start using project
|
||||
# namespaces in queries, we will have to extend this policy.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Namespaces
|
||||
class UserNamespacePolicy < BasePolicy
|
||||
class UserNamespacePolicy < ::NamespacePolicy
|
||||
rule { anonymous }.prevent_all
|
||||
|
||||
condition(:personal_project, scope: :subject) { @subject.kind == 'user' }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
.row.d-none.d-sm-flex
|
||||
.col-12.calendar-block.gl-my-3
|
||||
.user-calendar.light{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: local_time_instance(@user.timezone).now.utc_offset } }
|
||||
.user-calendar.light{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: local_timezone_instance(@user.timezone).now.utc_offset } }
|
||||
.gl-spinner.gl-spinner-md.gl-my-8
|
||||
.user-calendar-error.invisible
|
||||
= _('There was an error loading users activity calendar.')
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
%li
|
||||
%span.light.js-localtime{ :data => { :datetime => event.created_at.utc.strftime('%Y-%m-%dT%H:%M:%SZ'), :toggle => 'tooltip', :placement => 'top' } }
|
||||
= sprite_icon('clock', css_class: 'gl-vertical-align-text-bottom')
|
||||
= event.created_at.to_time.in_time_zone(local_time_instance(@user.timezone)).strftime('%-I:%M%P')
|
||||
= event.created_at.to_time.in_time_zone(local_timezone_instance(@user.timezone)).strftime('%-I:%M%P')
|
||||
- if event.visible_to_user?(current_user)
|
||||
- if event.push_action?
|
||||
#{event.action_name} #{event.ref_type}
|
||||
|
|
|
@ -4,5 +4,5 @@ introduced_by_url: https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3126
|
|||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/29875
|
||||
milestone: '12.0'
|
||||
type: development
|
||||
group: group::pipeline execution
|
||||
group: group::pipeline authoring
|
||||
default_enabled: true
|
||||
|
|
|
@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50922
|
|||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/296772
|
||||
milestone: '13.8'
|
||||
type: development
|
||||
group: group::pipeline execution
|
||||
group: group::testing
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateCalendarEventsIndexSynchronously < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_events_author_id_project_id_action_target_type_created_at'
|
||||
|
||||
def up
|
||||
add_concurrent_index :events, [:author_id, :project_id, :action, :target_type, :created_at], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :events, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class RemoveOldCalendarEventsIndex < Gitlab::Database::Migration[1.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
OLD_INDEX_NAME = 'index_events_on_author_id_and_project_id'
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :events, OLD_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :events, [:author_id, :project_id], name: OLD_INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
403592fda1d82ed3c3fb8d5315593b67954a4ecbc368d9bcd5eedc75bb3c9821
|
|
@ -0,0 +1 @@
|
|||
ba1c0d20e21ef51278109d0eaeb23f1c541eb5eb9aeb9a92583ee6de83c68918
|
|
@ -26018,14 +26018,14 @@ CREATE INDEX index_et_errors_on_project_id_and_status_first_seen_at_id_desc ON e
|
|||
|
||||
CREATE INDEX index_et_errors_on_project_id_and_status_last_seen_at_id_desc ON error_tracking_errors USING btree (project_id, status, last_seen_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX index_events_author_id_project_id_action_target_type_created_at ON events USING btree (author_id, project_id, action, target_type, created_at);
|
||||
|
||||
CREATE INDEX index_events_on_action ON events USING btree (action);
|
||||
|
||||
CREATE INDEX index_events_on_author_id_and_created_at ON events USING btree (author_id, created_at);
|
||||
|
||||
CREATE INDEX index_events_on_author_id_and_created_at_merge_requests ON events USING btree (author_id, created_at) WHERE ((target_type)::text = 'MergeRequest'::text);
|
||||
|
||||
CREATE INDEX index_events_on_author_id_and_project_id ON events USING btree (author_id, project_id);
|
||||
|
||||
CREATE INDEX index_events_on_created_at_and_id ON events USING btree (created_at, id) WHERE (created_at > '2021-08-27 00:00:00+00'::timestamp with time zone);
|
||||
|
||||
CREATE INDEX index_events_on_group_id_partial ON events USING btree (group_id) WHERE (group_id IS NOT NULL);
|
||||
|
|
|
@ -69,6 +69,8 @@ a single URL used by all Geo sites, including the primary.
|
|||
is using the secondary proxying and set the `URL` field to the single URL.
|
||||
Make sure the primary site is also using this URL.
|
||||
|
||||
In Kubernetes, you can use the same domain under `global.hosts.domain` as for the primary site.
|
||||
|
||||
## Disable Geo proxying
|
||||
|
||||
You can disable the secondary proxying on each Geo site, separately, by following these steps with Omnibus-based packages:
|
||||
|
@ -121,18 +123,22 @@ for details.
|
|||
|
||||
## Limitations
|
||||
|
||||
The asynchronous Geo replication can cause unexpected issues when secondary proxying is used, for accelerated
|
||||
data types that may be replicated to the Geo secondaries with a delay.
|
||||
- When secondary proxying is used, the asynchronous Geo replication can cause unexpected issues for accelerated
|
||||
data types that may be replicated to the Geo secondaries with a delay.
|
||||
|
||||
For example, we found a potential issue where
|
||||
[Replication lag introduces read-your-own-write inconsistencies](https://gitlab.com/gitlab-org/gitlab/-/issues/345267).
|
||||
If the replication lag is high enough, this can result in Git reads receiving stale data when hitting a secondary.
|
||||
For example, we found a potential issue where
|
||||
[replication lag introduces read-after-write inconsistencies](https://gitlab.com/gitlab-org/gitlab/-/issues/345267).
|
||||
If the replication lag is high enough, this can result in Git reads receiving stale data when hitting a secondary.
|
||||
|
||||
Non-Rails requests are not proxied, so other services may need to use a separate, non-unified URL to ensure requests
|
||||
are always sent to the primary. These services include:
|
||||
- Non-Rails requests are not proxied, so other services may need to use a separate, non-unified URL to ensure requests
|
||||
are always sent to the primary. These services include:
|
||||
|
||||
- GitLab Container Registry - [can be configured to use a separate domain](../../packages/container_registry.md#configure-container-registry-under-its-own-domain).
|
||||
- GitLab Pages - should always use a separate domain, as part of [the prerequisites for running GitLab Pages](../../pages/index.md#prerequisites).
|
||||
- GitLab Container Registry - [can be configured to use a separate domain](../../packages/container_registry.md#configure-container-registry-under-its-own-domain).
|
||||
- GitLab Pages - should always use a separate domain, as part of [the prerequisites for running GitLab Pages](../../pages/index.md#prerequisites).
|
||||
|
||||
- With a unified URL, Let's Encrypt can't generate certificates unless it can reach both IPs through the same domain.
|
||||
To use TLS certificates with Let's Encrypt, you can manually point the domain to one of the Geo sites, generate
|
||||
the certificate, then copy it to all other sites.
|
||||
|
||||
## Features accelerated by secondary Geo sites
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
|
||||
def initialize(contributor, current_user = nil)
|
||||
@contributor = contributor
|
||||
@contributor_time_instance = local_time_instance(contributor.timezone)
|
||||
@contributor_time_instance = local_timezone_instance(contributor.timezone).now
|
||||
@current_user = current_user
|
||||
@projects = if @contributor.include_private_contributions?
|
||||
ContributedProjectsFinder.new(@contributor).execute(@contributor)
|
||||
|
@ -24,18 +24,20 @@ module Gitlab
|
|||
return {} if @projects.empty?
|
||||
return @activity_dates if @activity_dates.present?
|
||||
|
||||
date_interval = "INTERVAL '#{@contributor_time_instance.now.utc_offset} seconds'"
|
||||
start_time = @contributor_time_instance.years_ago(1).beginning_of_day
|
||||
end_time = @contributor_time_instance.end_of_day
|
||||
|
||||
date_interval = "INTERVAL '#{@contributor_time_instance.utc_offset} seconds'"
|
||||
|
||||
# Can't use Event.contributions here because we need to check 3 different
|
||||
# project_features for the (currently) 3 different contribution types
|
||||
date_from = @contributor_time_instance.now.years_ago(1)
|
||||
repo_events = event_created_at(date_from, :repository)
|
||||
repo_events = events_created_between(start_time, end_time, :repository)
|
||||
.where(action: :pushed)
|
||||
issue_events = event_created_at(date_from, :issues)
|
||||
issue_events = events_created_between(start_time, end_time, :issues)
|
||||
.where(action: [:created, :closed], target_type: "Issue")
|
||||
mr_events = event_created_at(date_from, :merge_requests)
|
||||
mr_events = events_created_between(start_time, end_time, :merge_requests)
|
||||
.where(action: [:merged, :created, :closed], target_type: "MergeRequest")
|
||||
note_events = event_created_at(date_from, :merge_requests)
|
||||
note_events = events_created_between(start_time, end_time, :merge_requests)
|
||||
.where(action: :commented)
|
||||
|
||||
events = Event
|
||||
|
@ -54,7 +56,7 @@ module Gitlab
|
|||
def events_by_date(date)
|
||||
return Event.none unless can_read_cross_project?
|
||||
|
||||
date_in_time_zone = date.in_time_zone(@contributor_time_instance)
|
||||
date_in_time_zone = date.in_time_zone(@contributor_time_instance.time_zone)
|
||||
|
||||
Event.contributions.where(author_id: contributor.id)
|
||||
.where(created_at: date_in_time_zone.beginning_of_day..date_in_time_zone.end_of_day)
|
||||
|
@ -64,11 +66,11 @@ module Gitlab
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def starting_year
|
||||
@contributor_time_instance.now.years_ago(1).year
|
||||
@contributor_time_instance.years_ago(1).year
|
||||
end
|
||||
|
||||
def starting_month
|
||||
@contributor_time_instance.today.month
|
||||
@contributor_time_instance.month
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -78,9 +80,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def event_created_at(date_from, feature)
|
||||
t = Event.arel_table
|
||||
|
||||
def events_created_between(start_time, end_time, feature)
|
||||
# re-running the contributed projects query in each union is expensive, so
|
||||
# use IN(project_ids...) instead. It's the intersection of two users so
|
||||
# the list will be (relatively) short
|
||||
|
@ -89,24 +89,22 @@ module Gitlab
|
|||
# no need to check feature access of current user, if the contributor opted-in
|
||||
# to show all private events anyway - otherwise they would get filtered out again
|
||||
authed_projects = if @contributor.include_private_contributions?
|
||||
@contributed_project_ids.join(",")
|
||||
@contributed_project_ids
|
||||
else
|
||||
ProjectFeature
|
||||
.with_feature_available_for_user(feature, current_user)
|
||||
.where(project_id: @contributed_project_ids)
|
||||
.reorder(nil)
|
||||
.select(:project_id)
|
||||
.to_sql
|
||||
end
|
||||
|
||||
conditions = t[:created_at].gteq(date_from.beginning_of_day)
|
||||
.and(t[:created_at].lteq(@contributor_time_instance.today.end_of_day))
|
||||
.and(t[:author_id].eq(contributor.id))
|
||||
|
||||
Event.reorder(nil)
|
||||
.select(:created_at)
|
||||
.where(conditions)
|
||||
.where("events.project_id in (#{authed_projects})") # rubocop:disable GitlabSecurity/SqlInjection
|
||||
.where(
|
||||
author_id: contributor.id,
|
||||
created_at: start_time..end_time,
|
||||
events: { project_id: authed_projects }
|
||||
)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
@ -10202,6 +10202,9 @@ msgstr ""
|
|||
msgid "Critical vulnerabilities present"
|
||||
msgstr ""
|
||||
|
||||
msgid "Crm|Contact has been added"
|
||||
msgstr ""
|
||||
|
||||
msgid "Crm|Create new contact"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10220,6 +10223,9 @@ msgstr ""
|
|||
msgid "Crm|Last name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Crm|New Contact"
|
||||
msgstr ""
|
||||
|
||||
msgid "Crm|New contact"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -122,16 +122,6 @@ describe('Customer relations contacts root app', () => {
|
|||
|
||||
expect(findError().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should exist when new contact form emits error', async () => {
|
||||
router.replace({ path: '/new' });
|
||||
mountComponent();
|
||||
|
||||
findNewContactForm().vm.$emit('error');
|
||||
await waitForPromises();
|
||||
|
||||
expect(findError().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on successful load', () => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GlAlert } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
@ -21,6 +22,7 @@ describe('Customer relations contacts root app', () => {
|
|||
const findCreateNewContactButton = () => wrapper.findByTestId('create-new-contact-button');
|
||||
const findCancelButton = () => wrapper.findByTestId('cancel-button');
|
||||
const findForm = () => wrapper.find('form');
|
||||
const findError = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
const mountComponent = ({ mountFunction = shallowMountExtended } = {}) => {
|
||||
fakeApollo = createMockApollo([[createContactMutation, queryHandler]]);
|
||||
|
@ -32,6 +34,7 @@ describe('Customer relations contacts root app', () => {
|
|||
wrapper = mountFunction(NewContactForm, {
|
||||
provide: { groupId: 26, groupFullPath: 'flightjs' },
|
||||
apolloProvider: fakeApollo,
|
||||
propsData: { drawerOpen: true },
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -83,26 +86,25 @@ describe('Customer relations contacts root app', () => {
|
|||
});
|
||||
|
||||
describe('when query fails', () => {
|
||||
it('should emit error on reject', async () => {
|
||||
it('should show error on reject', async () => {
|
||||
queryHandler = jest.fn().mockRejectedValue('ERROR');
|
||||
mountComponent();
|
||||
|
||||
findForm().trigger('submit');
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted().error).toBeTruthy();
|
||||
expect(findError().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should emit error on error response', async () => {
|
||||
it('should show error on error response', async () => {
|
||||
queryHandler = jest.fn().mockResolvedValue(createContactMutationErrorResponse);
|
||||
mountComponent();
|
||||
|
||||
findForm().trigger('submit');
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted().error[0][0]).toEqual(
|
||||
createContactMutationErrorResponse.data.customerRelationsContactCreate.errors,
|
||||
);
|
||||
expect(findError().exists()).toBe(true);
|
||||
expect(findError().text()).toBe('Phone is invalid.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import StatusSelect from '~/issuable_bulk_update_sidebar/components/status_select.vue';
|
||||
import { ISSUE_STATUS_SELECT_OPTIONS } from '~/issuable_bulk_update_sidebar/constants';
|
||||
import StatusSelect from '~/issuable/bulk_update_sidebar/components/status_select.vue';
|
||||
import { ISSUE_STATUS_SELECT_OPTIONS } from '~/issuable/bulk_update_sidebar/constants';
|
||||
|
||||
describe('StatusSelect', () => {
|
||||
let wrapper;
|
|
@ -1,6 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
import IssuableForm from '~/issuable_form';
|
||||
import IssuableForm from '~/issuable/issuable_form';
|
||||
|
||||
function createIssuable() {
|
||||
const instance = new IssuableForm($(document.createElement('form')));
|
|
@ -1,5 +1,5 @@
|
|||
import issuableInitBulkUpdateSidebar from '~/issuable_bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
import IssuableIndex from '~/issuable_index';
|
||||
import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
|
||||
import IssuableIndex from '~/issuable/issuable_index';
|
||||
|
||||
describe('Issuable', () => {
|
||||
describe('initBulkUpdate', () => {
|
|
@ -2,7 +2,7 @@ import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/u
|
|||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import createFlash, { FLASH_TYPES } from '~/flash';
|
||||
import { IssuableType } from '~/issuable_show/constants';
|
||||
import { IssuableType } from '~/vue_shared/issuable/show/constants';
|
||||
import HeaderActions from '~/issues/show/components/header_actions.vue';
|
||||
import { IssuableStatus, IssueStateEvent } from '~/issues/show/constants';
|
||||
import promoteToEpicMutation from '~/issues/show/queries/promote_to_epic.mutation.graphql';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import initDeleteLabelModal from '~/delete_label_modal';
|
||||
import initDeleteLabelModal from '~/labels/delete_label_modal';
|
||||
|
||||
describe('DeleteLabelModal', () => {
|
||||
const buttons = [
|
|
@ -1,5 +1,5 @@
|
|||
import $ from 'jquery';
|
||||
import LabelsSelect from '~/labels_select';
|
||||
import LabelsSelect from '~/labels/labels_select';
|
||||
|
||||
const mockUrl = '/foo/bar/url';
|
||||
|
|
@ -86,7 +86,7 @@ exports[`packages_list_row renders 1`] = `
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="gl-w-9 gl-display-none gl-sm-display-flex gl-justify-content-end gl-pr-1"
|
||||
class="gl-w-9 gl-display-flex gl-justify-content-end gl-pr-1"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-label="Remove package"
|
||||
|
|
|
@ -92,7 +92,7 @@ exports[`packages_list_row renders 1`] = `
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="gl-w-9 gl-display-none gl-sm-display-flex gl-justify-content-end gl-pr-1"
|
||||
class="gl-w-9 gl-display-flex gl-justify-content-end gl-pr-1"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-label="Remove package"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
|
||||
import IssuableBody from '~/issuable_show/components/issuable_body.vue';
|
||||
import IssuableBody from '~/vue_shared/issuable/show/components/issuable_body.vue';
|
||||
|
||||
import IssuableDescription from '~/issuable_show/components/issuable_description.vue';
|
||||
import IssuableEditForm from '~/issuable_show/components/issuable_edit_form.vue';
|
||||
import IssuableTitle from '~/issuable_show/components/issuable_title.vue';
|
||||
import IssuableDescription from '~/vue_shared/issuable/show/components/issuable_description.vue';
|
||||
import IssuableEditForm from '~/vue_shared/issuable/show/components/issuable_edit_form.vue';
|
||||
import IssuableTitle from '~/vue_shared/issuable/show/components/issuable_title.vue';
|
||||
import TaskList from '~/task_list';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import $ from 'jquery';
|
||||
|
||||
import IssuableDescription from '~/issuable_show/components/issuable_description.vue';
|
||||
import IssuableDescription from '~/vue_shared/issuable/show/components/issuable_description.vue';
|
||||
|
||||
import { mockIssuable } from '../mock_data';
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { GlFormInput } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import IssuableEditForm from '~/issuable_show/components/issuable_edit_form.vue';
|
||||
import IssuableEventHub from '~/issuable_show/event_hub';
|
||||
import IssuableEditForm from '~/vue_shared/issuable/show/components/issuable_edit_form.vue';
|
||||
import IssuableEventHub from '~/vue_shared/issuable/show/event_hub';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
|
||||
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
|
|
@ -2,7 +2,7 @@ import { GlIcon, GlAvatarLabeled } from '@gitlab/ui';
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import IssuableHeader from '~/issuable_show/components/issuable_header.vue';
|
||||
import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
|
||||
|
||||
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import IssuableBody from '~/issuable_show/components/issuable_body.vue';
|
||||
import IssuableHeader from '~/issuable_show/components/issuable_header.vue';
|
||||
import IssuableShowRoot from '~/issuable_show/components/issuable_show_root.vue';
|
||||
import IssuableBody from '~/vue_shared/issuable/show/components/issuable_body.vue';
|
||||
import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
|
||||
import IssuableShowRoot from '~/vue_shared/issuable/show/components/issuable_show_root.vue';
|
||||
|
||||
import IssuableSidebar from '~/vue_shared/issuable/sidebar/components/issuable_sidebar_root.vue';
|
||||
|
|
@ -2,7 +2,7 @@ import { GlIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
|
||||
import IssuableTitle from '~/issuable_show/components/issuable_title.vue';
|
||||
import IssuableTitle from '~/vue_shared/issuable/show/components/issuable_title.vue';
|
||||
|
||||
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
|
||||
|
|
@ -125,7 +125,7 @@ RSpec.describe TimeZoneHelper, :aggregate_failures do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#local_time_instance' do
|
||||
describe '#local_timezone_instance' do
|
||||
let_it_be(:timezone) { 'UTC' }
|
||||
|
||||
before do
|
||||
|
@ -134,25 +134,25 @@ RSpec.describe TimeZoneHelper, :aggregate_failures do
|
|||
|
||||
context 'when timezone is `nil`' do
|
||||
it 'returns the system timezone instance' do
|
||||
expect(helper.local_time_instance(nil).name).to eq(timezone)
|
||||
expect(helper.local_timezone_instance(nil).name).to eq(timezone)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when timezone is blank' do
|
||||
it 'returns the system timezone instance' do
|
||||
expect(helper.local_time_instance('').name).to eq(timezone)
|
||||
expect(helper.local_timezone_instance('').name).to eq(timezone)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a valid timezone is passed' do
|
||||
it 'returns the local time instance' do
|
||||
expect(helper.local_time_instance('America/Los_Angeles').name).to eq('America/Los_Angeles')
|
||||
expect(helper.local_timezone_instance('America/Los_Angeles').name).to eq('America/Los_Angeles')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an invalid timezone is passed' do
|
||||
it 'returns the system timezone instance' do
|
||||
expect(helper.local_time_instance('Foo/Bar').name).to eq(timezone)
|
||||
expect(helper.local_timezone_instance('Foo/Bar').name).to eq(timezone)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -146,6 +146,7 @@ RSpec.describe Gitlab::ContributionsCalendar do
|
|||
create_event(public_project, today, 10)
|
||||
create_event(public_project, today, 16)
|
||||
create_event(public_project, today, 23)
|
||||
create_event(public_project, tomorrow, 1)
|
||||
end
|
||||
|
||||
it "renders correct event counts within the UTC timezone" do
|
||||
|
@ -158,14 +159,14 @@ RSpec.describe Gitlab::ContributionsCalendar do
|
|||
it "renders correct event counts within the Sydney timezone" do
|
||||
Time.use_zone('UTC') do
|
||||
contributor.timezone = 'Sydney'
|
||||
expect(calendar.activity_dates).to eq(today => 3, tomorrow => 2)
|
||||
expect(calendar.activity_dates).to eq(today => 3, tomorrow => 3)
|
||||
end
|
||||
end
|
||||
|
||||
it "renders correct event counts within the US Central timezone" do
|
||||
Time.use_zone('UTC') do
|
||||
contributor.timezone = 'Central Time (US & Canada)'
|
||||
expect(calendar.activity_dates).to eq(yesterday => 2, today => 3)
|
||||
expect(calendar.activity_dates).to eq(yesterday => 2, today => 4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe NamespacePolicy do
|
||||
RSpec.describe Namespaces::ProjectNamespacePolicy do
|
||||
let_it_be(:parent) { create(:namespace) }
|
||||
let_it_be(:project) { create(:project, namespace: parent) }
|
||||
let_it_be(:namespace) { project.project_namespace }
|
||||
|
@ -37,7 +37,7 @@ RSpec.describe NamespacePolicy do
|
|||
let_it_be(:current_user) { create(:admin) }
|
||||
|
||||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed(*permissions) }
|
||||
it { is_expected.to be_disallowed(*permissions) }
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
|
|
|
@ -209,6 +209,8 @@ RSpec.describe API::Ci::JobArtifacts do
|
|||
end
|
||||
|
||||
it 'returns specific job artifacts' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.headers.to_h).to include(download_headers)
|
||||
expect(response.body).to match_file(job.artifacts_file.file.file)
|
||||
|
@ -220,18 +222,48 @@ RSpec.describe API::Ci::JobArtifacts do
|
|||
context 'when artifacts are stored locally' do
|
||||
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
|
||||
|
||||
before do
|
||||
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
|
||||
end
|
||||
subject { get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user) }
|
||||
|
||||
context 'authorized user' do
|
||||
it_behaves_like 'downloads artifact'
|
||||
end
|
||||
|
||||
context 'when job token is used' do
|
||||
let(:other_job) { create(:ci_build, :running, user: user) }
|
||||
|
||||
subject { get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", job_token: other_job.token) }
|
||||
|
||||
before do
|
||||
stub_licensed_features(cross_project_pipelines: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'downloads artifact'
|
||||
|
||||
context 'when job token scope is enabled' do
|
||||
before do
|
||||
other_job.project.ci_cd_settings.update!(job_token_scope_enabled: true)
|
||||
end
|
||||
|
||||
it 'does not allow downloading artifacts' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
|
||||
context 'when project is added to the job token scope' do
|
||||
let!(:link) { create(:ci_job_token_project_scope_link, source_project: other_job.project, target_project: job.project) }
|
||||
|
||||
it_behaves_like 'downloads artifact'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthorized user' do
|
||||
let(:api_user) { nil }
|
||||
|
||||
it 'does not return specific job artifacts' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,13 +19,15 @@ module ApiHelpers
|
|||
# => "/api/v2/issues?foo=bar&private_token=..."
|
||||
#
|
||||
# Returns the relative path to the requested API resource
|
||||
def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil)
|
||||
def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil, job_token: nil)
|
||||
full_path = "/api/#{version}#{path}"
|
||||
|
||||
if oauth_access_token
|
||||
query_string = "access_token=#{oauth_access_token.token}"
|
||||
elsif personal_access_token
|
||||
query_string = "private_token=#{personal_access_token.token}"
|
||||
elsif job_token
|
||||
query_string = "job_token=#{job_token}"
|
||||
elsif user
|
||||
personal_access_token = create(:personal_access_token, user: user)
|
||||
query_string = "private_token=#{personal_access_token.token}"
|
||||
|
|
Loading…
Reference in New Issue